Iammcqwory commited on
Commit
25aa262
ยท
verified ยท
1 Parent(s): add40a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +457 -248
app.py CHANGED
@@ -4,17 +4,17 @@ import time
4
  from datetime import datetime
5
  import base64
6
 
7
- # App Title and Config
8
  st.set_page_config(page_title="Gamuu", page_icon="๐ŸŽฎ", layout="wide")
9
  st.title("๐ŸŽฎ Gamuu - Ultimate Connection Game")
10
 
11
- # Initialize Session State
12
  if 'registered' not in st.session_state:
13
- st.session_state.registered = False
14
  if 'players' not in st.session_state:
15
- st.session_state.players = {}
16
  if 'teams' not in st.session_state:
17
- st.session_state.teams = {}
18
  if 'questions' not in st.session_state:
19
  st.session_state.questions = {
20
  'Music': [
@@ -336,7 +336,7 @@ if 'questions' not in st.session_state:
336
  "What's your favorite food to eat at a festival?",
337
  "What's your favorite food to eat at a theme park?",
338
  "What's your favorite food to eat at a concert?",
339
- "What's your favorite food to eat at a sporting event?",
340
  "What's your favorite food to eat at a carnival?",
341
  "What's your favorite food to eat at a fair?",
342
  "What's your favorite food to eat at a holiday gathering?",
@@ -411,7 +411,7 @@ if 'questions' not in st.session_state:
411
  if 'points' not in st.session_state:
412
  st.session_state.points = {} # Individual player points
413
  if 'team_points' not in st.session_state:
414
- st.session_state.team_points = {} # Team points
415
  if 'feedback' not in st.session_state:
416
  st.session_state.feedback = []
417
  if 'suggestions' not in st.session_state:
@@ -422,10 +422,12 @@ if 'question' not in st.session_state:
422
  st.session_state.question = None
423
  if 'timer_start' not in st.session_state:
424
  st.session_state.timer_start = None
 
 
425
  if 'achievements' not in st.session_state:
426
- st.session_state.achievements = {}
427
  if 'timeouts' not in st.session_state:
428
- st.session_state.timeouts = {}
429
  if 'admin' not in st.session_state:
430
  st.session_state.admin = False
431
  if 'round' not in st.session_state:
@@ -436,7 +438,7 @@ if 'penalty_threshold' not in st.session_state:
436
  st.session_state.penalty_threshold = 3
437
  if 'game_paused' not in st.session_state:
438
  st.session_state.game_paused = False
439
- if 'available_categories' not in st.session_state:
440
  st.session_state.available_categories = []
441
  if 'theme' not in st.session_state:
442
  st.session_state.theme = "Light"
@@ -449,7 +451,7 @@ if 'current_category_index' not in st.session_state:
449
  if 'round_start_time' not in st.session_state:
450
  st.session_state.round_start_time = None
451
 
452
- # Theme CSS
453
  def apply_theme():
454
  if st.session_state.theme == "Dark":
455
  st.markdown("""
@@ -474,69 +476,98 @@ def apply_theme():
474
  <style>
475
  body { background-color: #ffffff; color: #000000; }
476
  .stApp { background-color: #ffffff; color: #000000; }
 
477
  </style>
478
  """, unsafe_allow_html=True)
479
 
480
- # Non-blocking Timer HTML
481
  timer_html = """
482
  <script>
483
- let timeLeft = 30;
484
- let timer = setInterval(() => {
485
- if (timeLeft > 0 && !window.paused) {
486
- timeLeft--;
487
- document.getElementById("timer").innerHTML = `โณ Time Remaining: ${timeLeft}s`;
488
- document.getElementById("progress").value = 30 - timeLeft;
489
- } else if (timeLeft <= 0) {
490
- clearInterval(timer);
491
- document.getElementById("timer").innerHTML = "โฐ Time's up!";
492
- }
493
- }, 1000);
494
-
495
- function pauseTimer() {
496
- window.paused = true;
 
 
 
 
 
497
  }
498
 
499
- function resumeTimer() {
500
- window.paused = false;
501
- }
 
 
 
 
502
  </script>
503
  <div id="timer">โณ Time Remaining: 30s</div>
504
- <progress id="progress" value="0" max="30"></progress>
505
  """
506
 
507
- # Sidebar - Registration and Socials
 
 
 
 
508
  with st.sidebar:
509
  st.header("๐Ÿš€ Player Registration")
510
- with st.form("registration"):
511
- player_name = st.text_input("Your Name")
512
- team_name = st.text_input("Team Name (leave blank for solo)")
513
- all_categories = ['Music', 'Love Language', 'Movies', 'Activities', '18+', 'Travel', 'Food', 'Personal Growth', 'Dare']
514
- categories = st.multiselect("Choose Categories", all_categories, key=f"categories_{len(st.session_state.players)}")
515
- avatar_file = st.file_uploader("Upload Avatar (optional)", type=["png", "jpg", "jpeg"])
516
- submitted = st.form_submit_button("Join Game")
517
- if submitted and player_name:
518
- avatar_data = avatar_file.read() if avatar_file else None
519
- st.session_state.players[player_name] = {
520
- 'categories': categories,
521
- 'joined': datetime.now(),
522
- 'team': team_name if team_name else player_name,
523
- 'avatar': base64.b64encode(avatar_data).decode('utf-8') if avatar_data else None
524
- }
525
- st.session_state.points[player_name] = 0
526
- st.session_state.timeouts[player_name] = 0
527
- if team_name:
528
- if team_name not in st.session_state.teams:
529
- st.session_state.teams[team_name] = []
530
- st.session_state.team_points[team_name] = 0
531
- st.session_state.teams[team_name].append(player_name)
532
- st.session_state.registered = True
533
- st.session_state.available_categories = list(set().union(*[p['categories'] for p in st.session_state.players.values()]))
534
- st.success(f"Welcome {player_name}! You've joined the game.")
535
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
537
  st.header("๐ŸŽจ Theme Selection")
538
- st.session_state.theme = st.selectbox("Choose Theme", ["Light", "Dark", "Retro"], index=["Light", "Dark", "Retro"].index(st.session_state.theme))
539
- apply_theme()
540
 
541
  st.header("๐Ÿ“ฑ Social Connect")
542
  st.write("๐Ÿ”— [Instagram](https://instagram.com) | [Twitter](https://twitter.com)")
@@ -544,265 +575,443 @@ with st.sidebar:
544
 
545
  st.header("๐Ÿ’ก Feedback & Suggestions")
546
  with st.form("feedback_form"):
547
- feedback = st.text_area("Share your experience or suggest improvements")
 
548
  if st.form_submit_button("Submit Feedback"):
549
- if player_name:
550
- st.session_state.feedback.append(feedback)
551
- st.session_state.points[player_name] += 5
552
- st.success("Thanks for your feedback! +5 points")
 
 
 
553
  else:
554
- st.error("Please register first!")
 
555
 
556
  st.header("๐Ÿ”’ Admin Login")
557
- admin_password = st.text_input("Admin Password", type="password")
558
- if st.button("Login as Admin"):
559
- if admin_password == "admin123": # Replace with a secure method
560
  st.session_state.admin = True
561
  st.success("Admin login successful!")
 
562
  else:
563
  st.error("Incorrect password.")
564
 
565
- # Admin Dashboard
566
  if st.session_state.admin:
567
  st.sidebar.header("๐Ÿ”ง Admin Dashboard")
568
  admin_tab1, admin_tab2 = st.sidebar.tabs(["Manage Questions", "Game Settings"])
569
 
570
  with admin_tab1:
571
  st.subheader("Add New Question")
572
- with st.form("add_question"):
573
- category = st.selectbox("Category", list(st.session_state.questions.keys()))
574
- new_question = st.text_input("New Question")
 
 
 
 
575
  if st.form_submit_button("Add Question"):
576
- if category in st.session_state.questions:
577
- if isinstance(st.session_state.questions[category], dict):
578
- subcategory = st.selectbox("Subcategory", list(st.session_state.questions[category].keys()))
579
- st.session_state.questions[category][subcategory].append(new_question)
 
580
  else:
581
- st.session_state.questions[category].append(new_question)
582
  st.success("Question added successfully!")
583
  else:
584
- st.error("Category not found.")
585
 
586
  st.subheader("Remove Question")
587
- with st.form("remove_question"):
588
- category = st.selectbox("Category to Remove From", list(st.session_state.questions.keys()))
589
- if isinstance(st.session_state.questions[category], dict):
590
- subcategory = st.selectbox("Subcategory", list(st.session_state.questions[category].keys()))
591
- question_to_remove = st.selectbox("Question to Remove", st.session_state.questions[category][subcategory])
592
- else:
593
- question_to_remove = st.selectbox("Question to Remove", st.session_state.questions[category])
594
- if st.form_submit_button("Remove Question"):
595
- if isinstance(st.session_state.questions[category], dict):
596
- st.session_state.questions[category][subcategory].remove(question_to_remove)
597
- else:
598
- st.session_state.questions[category].remove(question_to_remove)
599
- st.success("Question removed successfully!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
  with admin_tab2:
602
  st.subheader("Game Settings")
603
- penalty_threshold = st.number_input("Penalty Threshold", min_value=1, value=st.session_state.penalty_threshold)
604
- dare_frequency = st.number_input("Dare Frequency (Rounds)", min_value=1, value=st.session_state.dare_frequency)
605
- if st.button("Save Settings"):
606
- st.session_state.penalty_threshold = penalty_threshold
607
- st.session_state.dare_frequency = dare_frequency
608
- st.success(f"Settings saved! Penalty Threshold: {penalty_threshold}, Dare Frequency: every {dare_frequency} rounds.")
609
-
610
- # Main Game Interface
611
  tab1, tab2, tab3, tab4 = st.tabs(["๐ŸŽฏ Game Zone", "๐Ÿ† Leaderboard", "๐ŸŽ Redeem Points", "๐ŸŒŸ Achievements"])
612
 
613
  with tab1:
614
- if st.session_state.registered:
615
  st.header("๐ŸŒŸ Current Round")
616
  col1, col2 = st.columns([3, 2])
617
- players = list(st.session_state.players.keys())
618
 
619
  with col1:
620
- if st.button("โธ๏ธ Pause Game" if not st.session_state.game_paused else "โ–ถ๏ธ Resume Game"):
621
  st.session_state.game_paused = not st.session_state.game_paused
622
  if st.session_state.game_paused:
623
  st.warning("Game Paused!")
624
- st.components.v1.html("<script>pauseTimer();</script>", height=0)
625
  else:
626
  st.success("Game Resumed!")
627
- st.components.v1.html("<script>resumeTimer();</script>", height=0)
 
 
628
 
629
  if not st.session_state.game_paused:
630
- # Round Timer (5 minutes = 300 seconds)
631
  if st.session_state.round_start_time is None:
632
  st.session_state.round_start_time = time.time()
633
- round_time_left = 300 - int(time.time() - st.session_state.round_start_time)
 
 
 
 
634
  if round_time_left <= 0:
 
635
  st.session_state.current_player = None
636
  st.session_state.question = None
637
- st.session_state.timer_start = None
638
- st.session_state.round_start_time = time.time()
639
  st.session_state.question_count = 0
640
  st.session_state.current_category_index = 0
641
- st.warning("Round ended! Moving to next round.")
642
-
643
- st.write(f"โณ Round Time Left: {round_time_left}s")
644
-
645
- # Category Cycling
646
- available_categories = st.session_state.available_categories
647
- if st.session_state.question_count >= 9 and len(available_categories) > 1:
648
- st.session_state.current_category_index = (st.session_state.current_category_index + 1) % len(available_categories)
649
- st.session_state.question_count = 0
650
- st.success(f"Switching to category: {available_categories[st.session_state.current_category_index]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
- selected_category = st.selectbox("Choose Question Category",
653
- available_categories + ['Random'], index=st.session_state.current_category_index)
654
 
655
- if selected_category == '18+' and '18+' in available_categories:
656
- spice_level = st.select_slider("๐ŸŒถ๏ธ Spice Level",
657
- options=['Spicy', 'Extra Spicy', 'Space Spicy', 'Hardcore'])
 
658
 
659
- if st.button("๐ŸŒ€ Spin the Bottle", use_container_width=True):
660
- if len(players) > 1:
661
- st.session_state.current_player = random.choice(players)
662
- st.session_state.timer_start = time.time()
663
  st.session_state.round += 1
664
  st.session_state.question_count += 1
 
 
665
 
666
- if selected_category == '18+':
667
- st.session_state.question = random.choice(st.session_state.questions['18+'][spice_level])
668
- elif selected_category == 'Random':
669
- category = random.choice(available_categories)
670
- if isinstance(st.session_state.questions[category], dict):
671
- subcategory = random.choice(list(st.session_state.questions[category].keys()))
672
- st.session_state.question = random.choice(st.session_state.questions[category][subcategory])
673
- else:
674
- st.session_state.question = random.choice(st.session_state.questions[category])
 
 
675
  else:
676
- st.session_state.question = random.choice(st.session_state.questions[selected_category])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
 
678
- if st.session_state.round % st.session_state.dare_frequency == 0 and 'Dare' in available_categories:
679
- st.session_state.question = random.choice(st.session_state.questions['Dare'])
 
 
 
 
 
 
680
 
681
- st.success(f"๐ŸŽฏ {st.session_state.current_player}, it's your turn!")
682
- st.session_state.round_start_time = time.time() if st.session_state.round_start_time is None else st.session_state.round_start_time
683
  else:
684
  st.error("Not enough players to spin the bottle!")
 
 
685
 
 
 
686
  if st.session_state.current_player and st.session_state.question and not st.session_state.game_paused:
687
  with col2:
688
- player_avatar = st.session_state.players[st.session_state.current_player]['avatar']
689
- if player_avatar:
690
- st.image(f"data:image/png;base64,{player_avatar}", width=100)
691
- st.subheader(f"๐ŸŽฏ Current Player: {st.session_state.current_player}")
692
- st.subheader("โ“ Your Question:")
693
- st.write(f"**{st.session_state.question}**")
694
-
695
- # Non-blocking Timer
696
- timer_placeholder = st.components.v1.html(timer_html, height=70)
697
- if st.session_state.timer_start and time.time() - st.session_state.timer_start >= 30:
698
- st.error("โฐ Time's up! Points deducted")
699
- st.session_state.points[st.session_state.current_player] -= 10
700
- st.session_state.timeouts[st.session_state.current_player] += 1
701
- st.session_state.timer_start = None
702
-
703
- # Penalty System
704
- if st.session_state.timeouts[st.session_state.current_player] >= st.session_state.penalty_threshold:
705
- st.error(f"{st.session_state.current_player} has timed out too many times and is penalized!")
706
- savior = st.selectbox("Choose a Savior", [p for p in players if p != st.session_state.current_player])
707
- ultimate_question = random.choice(st.session_state.questions['18+']['Hardcore'])
708
- st.write(f"{savior}, answer this ultimate question to save {st.session_state.current_player}:")
709
- st.write(f"**{ultimate_question}**")
710
- if st.button("Submit Answer"):
711
- st.success(f"{savior} has saved {st.session_state.current_player}!")
712
- st.session_state.timeouts[st.session_state.current_player] = 0
713
-
714
- if st.button("โœ… Submit Answer", key="answer_submit"):
715
- time_taken = int(time.time() - st.session_state.timer_start)
716
- points = max(50 - time_taken, 10)
717
- st.session_state.points[st.session_state.current_player] += points
718
- team = st.session_state.players[st.session_state.current_player]['team']
719
- if team != st.session_state.current_player: # If part of a team
720
- st.session_state.team_points[team] += points
721
- st.success(f"๐ŸŽ‰ Answer accepted! +{points} points")
722
- st.session_state.current_player = None
723
- st.session_state.question = None
724
- st.session_state.timer_start = None
725
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  with st.expander("๐Ÿ’ก Suggest a New Question"):
727
- with st.form("question_suggestion"):
728
- new_category = st.selectbox("Category", list(st.session_state.questions.keys()))
729
- new_question = st.text_input("Your Question")
730
- if st.form_submit_button("Suggest"):
731
- if player_name:
732
- st.session_state.suggestions.append((new_category, new_question))
733
- st.session_state.points[player_name] += 15
734
- st.success("Question submitted! +15 points")
 
 
735
  else:
736
- st.error("Please register first!")
 
 
 
737
 
738
- with tab2:
739
  st.header("๐Ÿ… Player Leaderboard")
740
- sorted_players = sorted(st.session_state.points.items(), key=lambda x: x[1], reverse=True)
741
- for i, (player, points) in enumerate(sorted_players):
742
- avatar = st.session_state.players[player]['avatar']
743
- if avatar:
744
- st.image(f"data:image/png;base64,{avatar}", width=50)
745
- st.metric(f"๐Ÿฅ‡ {i+1}. {player}", f"{points} points")
746
-
747
- st.header("๐Ÿ… Team Leaderboard")
748
- sorted_teams = sorted(st.session_state.team_points.items(), key=lambda x: x[1], reverse=True)
749
- for i, (team, points) in enumerate(sorted_teams):
750
- st.metric(f"๐Ÿฅ‡ {i+1}. Team {team}", f"{points} points")
751
-
752
- st.header("๐Ÿ… Leaderboard History")
753
- if st.button("Save Current Leaderboard"):
754
- history_entry = {
755
- 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
756
- 'players': dict(sorted_players),
757
- 'teams': dict(sorted_teams)
758
- }
759
- st.session_state.leaderboard_history.append(history_entry)
760
- st.success("Leaderboard saved!")
761
- for entry in st.session_state.leaderboard_history:
762
- st.write(f"**{entry['date']}**")
763
- st.write("Players:", entry['players'])
764
- st.write("Teams:", entry['teams'])
765
-
766
- with tab3:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
  st.header("๐Ÿ›๏ธ Redeem Points")
768
- if player_name and player_name in st.session_state.points:
769
- current_points = st.session_state.points[player_name]
770
- st.write(f"Your available points: {current_points}")
 
 
 
 
 
 
 
 
771
 
772
- col1, col2, col3 = st.columns(3)
773
- with col1:
774
- st.subheader("๐ŸŽ Basic Rewards")
775
- if st.button("Custom Emoji (50 points)"):
776
- if current_points >= 50:
777
- st.session_state.points[player_name] -= 50
778
- st.success("๐ŸŽ‰ Emoji unlocked!")
779
- with col2:
780
- st.subheader("๐Ÿ’Ž Premium Rewards")
781
- if st.button("Special Badge (200 points)"):
782
- if current_points >= 200:
783
- st.session_state.points[player_name] -= 200
784
- st.success("๐ŸŒŸ Badge awarded!")
785
- with col3:
786
- st.subheader("๐Ÿ”ฅ Spicy Rewards")
787
- if st.button("18+ Question Pack (300 points)"):
788
- if current_points >= 300:
789
- st.session_state.points[player_name] -= 300
790
- st.success("๐Ÿ”ž Pack unlocked!")
791
  else:
792
- st.warning("Please register to redeem points.")
793
 
794
- with tab4:
795
  st.header("๐ŸŒŸ Achievements")
796
- if player_name:
797
- st.write(f"๐Ÿ† {player_name}'s Achievements:")
798
- if st.session_state.achievements.get(player_name):
799
- for achievement in st.session_state.achievements[player_name]:
800
- st.write(f"- {achievement}")
 
 
 
801
  else:
802
- st.write("No achievements yet. Keep playing!")
803
  else:
804
- st.warning("Please register to view achievements.")
805
 
806
- # Footer
807
  st.markdown("---")
808
- st.markdown("**๐ŸŽฎ Gamuu** - Where Connections Get Interesting! โ‹… v3.0 โ‹… [Privacy Policy] โ‹… [Terms of Service]")
 
 
4
  from datetime import datetime
5
  import base64
6
 
7
+ # --- App Title and Config ---
8
  st.set_page_config(page_title="Gamuu", page_icon="๐ŸŽฎ", layout="wide")
9
  st.title("๐ŸŽฎ Gamuu - Ultimate Connection Game")
10
 
11
+ # --- Initialize Session State (with some additions for robustness) ---
12
  if 'registered' not in st.session_state:
13
+ st.session_state.registered = False # True if at least one player is registered
14
  if 'players' not in st.session_state:
15
+ st.session_state.players = {} # {name: {details}}
16
  if 'teams' not in st.session_state:
17
+ st.session_state.teams = {} # {team_name: [player_name1, player_name2]} - for multi-player teams
18
  if 'questions' not in st.session_state:
19
  st.session_state.questions = {
20
  'Music': [
 
336
  "What's your favorite food to eat at a festival?",
337
  "What's your favorite food to eat at a theme park?",
338
  "What's your favorite food to eat at a concert?",
339
+ "What's_your favorite food to eat at a sporting event?",
340
  "What's your favorite food to eat at a carnival?",
341
  "What's your favorite food to eat at a fair?",
342
  "What's your favorite food to eat at a holiday gathering?",
 
411
  if 'points' not in st.session_state:
412
  st.session_state.points = {} # Individual player points
413
  if 'team_points' not in st.session_state:
414
+ st.session_state.team_points = {} # Team points (for multi-player teams)
415
  if 'feedback' not in st.session_state:
416
  st.session_state.feedback = []
417
  if 'suggestions' not in st.session_state:
 
422
  st.session_state.question = None
423
  if 'timer_start' not in st.session_state:
424
  st.session_state.timer_start = None
425
+ if 'timer_instance_key' not in st.session_state: # Key for HTML timer component
426
+ st.session_state.timer_instance_key = "timer_initial"
427
  if 'achievements' not in st.session_state:
428
+ st.session_state.achievements = {} # {player_name: [achievement1, ...]}
429
  if 'timeouts' not in st.session_state:
430
+ st.session_state.timeouts = {} # {player_name: count}
431
  if 'admin' not in st.session_state:
432
  st.session_state.admin = False
433
  if 'round' not in st.session_state:
 
438
  st.session_state.penalty_threshold = 3
439
  if 'game_paused' not in st.session_state:
440
  st.session_state.game_paused = False
441
+ if 'available_categories' not in st.session_state: # Categories chosen by at least one player
442
  st.session_state.available_categories = []
443
  if 'theme' not in st.session_state:
444
  st.session_state.theme = "Light"
 
451
  if 'round_start_time' not in st.session_state:
452
  st.session_state.round_start_time = None
453
 
454
+ # --- Theme CSS ---
455
  def apply_theme():
456
  if st.session_state.theme == "Dark":
457
  st.markdown("""
 
476
  <style>
477
  body { background-color: #ffffff; color: #000000; }
478
  .stApp { background-color: #ffffff; color: #000000; }
479
+ /* You might want to remove button styling for light theme or make it default */
480
  </style>
481
  """, unsafe_allow_html=True)
482
 
483
+ # --- Non-blocking Timer HTML ---
484
  timer_html = """
485
  <script>
486
+ let timeLeft = 30; // This will be visually reset, but actual timeout is Python-driven
487
+ let timerInterval; // Store interval ID to clear it
488
+
489
+ function startTimer() {
490
+ clearInterval(timerInterval); // Clear any existing timer
491
+ timeLeft = 30; // Reset time
492
+ document.getElementById("timer").innerHTML = `โณ Time Remaining: ${timeLeft}s`;
493
+ document.getElementById("progress").value = 0;
494
+
495
+ timerInterval = setInterval(() => {
496
+ if (timeLeft > 0 && !window.gamuuPaused) { // Check a global pause flag
497
+ timeLeft--;
498
+ document.getElementById("timer").innerHTML = `โณ Time Remaining: ${timeLeft}s`;
499
+ document.getElementById("progress").value = 30 - timeLeft;
500
+ } else if (timeLeft <= 0) {
501
+ clearInterval(timerInterval);
502
+ document.getElementById("timer").innerHTML = "โฐ Time's up!";
503
+ }
504
+ }, 1000);
505
  }
506
 
507
+ // Functions to be called by Streamlit via st.components.v1.html
508
+ window.pauseGamuuTimer = function() { window.gamuuPaused = true; }
509
+ window.resumeGamuuTimer = function() { window.gamuuPaused = false; }
510
+
511
+ // Start timer on initial load of this component
512
+ // This script block re-runs when the component is re-rendered with a new key.
513
+ startTimer();
514
  </script>
515
  <div id="timer">โณ Time Remaining: 30s</div>
516
+ <progress id="progress" value="0" max="30" style="width: 100%;"></progress>
517
  """
518
 
519
+ # --- Helper to get registered players for selectboxes ---
520
+ def get_registered_player_names():
521
+ return list(st.session_state.players.keys()) if st.session_state.players else []
522
+
523
+ # --- Sidebar ---
524
  with st.sidebar:
525
  st.header("๐Ÿš€ Player Registration")
526
+ with st.form("registration_form"): # Added form key
527
+ player_name_input = st.text_input("Your Name")
528
+ team_name_input = st.text_input("Team Name (leave blank for solo)")
529
+ all_categories_list = ['Music', 'Love Language', 'Movies', 'Activities', '18+', 'Travel', 'Food', 'Personal Growth', 'Dare']
530
+ # Use a dynamic key for multiselect to avoid issues if players have same name (though names are keys now)
531
+ categories_selected = st.multiselect("Choose Categories", all_categories_list, key=f"categories_input_{len(st.session_state.players)}")
532
+ avatar_file_uploaded = st.file_uploader("Upload Avatar (optional)", type=["png", "jpg", "jpeg"])
533
+ submitted_registration = st.form_submit_button("Join Game")
534
+
535
+ if submitted_registration and player_name_input:
536
+ if player_name_input in st.session_state.players:
537
+ st.error(f"Player name '{player_name_input}' already taken!")
538
+ else:
539
+ avatar_data_bytes = avatar_file_uploaded.read() if avatar_file_uploaded else None
540
+ player_team_key = team_name_input if team_name_input else player_name_input # Player's own name if solo
541
+
542
+ st.session_state.players[player_name_input] = {
543
+ 'categories': categories_selected,
544
+ 'joined': datetime.now(),
545
+ 'team': player_team_key,
546
+ 'avatar': base64.b64encode(avatar_data_bytes).decode('utf-8') if avatar_data_bytes else None
547
+ }
548
+ st.session_state.points[player_name_input] = 0
549
+ st.session_state.timeouts[player_name_input] = 0
550
+
551
+ if team_name_input: # If it's a multi-player team
552
+ if team_name_input not in st.session_state.teams:
553
+ st.session_state.teams[team_name_input] = []
554
+ st.session_state.team_points[team_name_input] = 0 # Initialize points for new multi-player team
555
+ st.session_state.teams[team_name_input].append(player_name_input)
556
+
557
+ st.session_state.registered = True # At least one player is now registered
558
+
559
+ # Update available_categories based on ALL registered players
560
+ all_player_categories = set()
561
+ for p_data in st.session_state.players.values():
562
+ all_player_categories.update(p_data['categories'])
563
+ st.session_state.available_categories = sorted(list(all_player_categories)) # Sorted for consistent order
564
+
565
+ st.success(f"Welcome {player_name_input}! You've joined the game.")
566
+ st.rerun() # Rerun to update UI elements that depend on player list
567
 
568
  st.header("๐ŸŽจ Theme Selection")
569
+ st.session_state.theme = st.selectbox("Choose Theme", ["Light", "Dark", "Retro"], index=["Light", "Dark", "Retro"].index(st.session_state.theme), key="theme_selector")
570
+ apply_theme() # Apply theme immediately
571
 
572
  st.header("๐Ÿ“ฑ Social Connect")
573
  st.write("๐Ÿ”— [Instagram](https://instagram.com) | [Twitter](https://twitter.com)")
 
575
 
576
  st.header("๐Ÿ’ก Feedback & Suggestions")
577
  with st.form("feedback_form"):
578
+ feedback_text = st.text_area("Share your experience or suggest improvements")
579
+ feedback_player_name = st.selectbox("Submitting as:", get_registered_player_names(), key="feedback_player", help="Select your player name.")
580
  if st.form_submit_button("Submit Feedback"):
581
+ if feedback_player_name and feedback_text: # Check if a player is selected and text is provided
582
+ st.session_state.feedback.append({'player': feedback_player_name, 'feedback': feedback_text, 'time': datetime.now()})
583
+ st.session_state.points[feedback_player_name] = st.session_state.points.get(feedback_player_name, 0) + 5
584
+ st.success(f"Thanks for your feedback, {feedback_player_name}! +5 points")
585
+ st.rerun()
586
+ elif not feedback_player_name:
587
+ st.error("Please select your player name to submit feedback.")
588
  else:
589
+ st.error("Please write some feedback before submitting.")
590
+
591
 
592
  st.header("๐Ÿ”’ Admin Login")
593
+ admin_password_input = st.text_input("Admin Password", type="password", key="admin_pass")
594
+ if st.button("Login as Admin", key="admin_login_btn"):
595
+ if admin_password_input == "admin123": # Replace with a secure method (e.g., Streamlit Secrets)
596
  st.session_state.admin = True
597
  st.success("Admin login successful!")
598
+ st.rerun()
599
  else:
600
  st.error("Incorrect password.")
601
 
602
+ # --- Admin Dashboard (if logged in) ---
603
  if st.session_state.admin:
604
  st.sidebar.header("๐Ÿ”ง Admin Dashboard")
605
  admin_tab1, admin_tab2 = st.sidebar.tabs(["Manage Questions", "Game Settings"])
606
 
607
  with admin_tab1:
608
  st.subheader("Add New Question")
609
+ with st.form("add_question_form"):
610
+ add_q_category = st.selectbox("Category", list(st.session_state.questions.keys()), key="admin_add_cat")
611
+ add_q_subcategory = None
612
+ if isinstance(st.session_state.questions.get(add_q_category), dict):
613
+ add_q_subcategory = st.selectbox("Subcategory", list(st.session_state.questions[add_q_category].keys()), key="admin_add_subcat")
614
+
615
+ new_question_text = st.text_input("New Question", key="admin_new_q_text")
616
  if st.form_submit_button("Add Question"):
617
+ if new_question_text:
618
+ if add_q_subcategory:
619
+ st.session_state.questions[add_q_category][add_q_subcategory].append(new_question_text)
620
+ elif add_q_category in st.session_state.questions and isinstance(st.session_state.questions[add_q_category], list):
621
+ st.session_state.questions[add_q_category].append(new_question_text)
622
  else:
623
+ st.error(f"Cannot add question to category '{add_q_category}' structure.") # Should not happen with current setup
624
  st.success("Question added successfully!")
625
  else:
626
+ st.error("Question text cannot be empty.")
627
 
628
  st.subheader("Remove Question")
629
+ with st.form("remove_question_form"):
630
+ rem_q_category = st.selectbox("Category to Remove From", list(st.session_state.questions.keys()), key="admin_rem_cat")
631
+ rem_q_subcategory = None
632
+ questions_for_removal = []
633
+
634
+ if rem_q_category in st.session_state.questions:
635
+ if isinstance(st.session_state.questions[rem_q_category], dict):
636
+ rem_q_subcategory = st.selectbox("Subcategory", list(st.session_state.questions[rem_q_category].keys()), key="admin_rem_subcat")
637
+ if rem_q_subcategory and rem_q_subcategory in st.session_state.questions[rem_q_category]:
638
+ questions_for_removal = st.session_state.questions[rem_q_category][rem_q_subcategory]
639
+ elif isinstance(st.session_state.questions[rem_q_category], list):
640
+ questions_for_removal = st.session_state.questions[rem_q_category]
641
+
642
+ question_to_remove_text = st.selectbox("Question to Remove", questions_for_removal, key="admin_rem_q_select", disabled=not questions_for_removal)
643
+
644
+ if st.form_submit_button("Remove Question", disabled=not question_to_remove_text):
645
+ try:
646
+ if rem_q_subcategory:
647
+ st.session_state.questions[rem_q_category][rem_q_subcategory].remove(question_to_remove_text)
648
+ else:
649
+ st.session_state.questions[rem_q_category].remove(question_to_remove_text)
650
+ st.success("Question removed successfully!")
651
+ st.rerun() # To update the selectbox
652
+ except ValueError:
653
+ st.error("Question not found for removal (should not happen with selectbox).")
654
+ except Exception as e:
655
+ st.error(f"Error removing question: {e}")
656
+
657
 
658
  with admin_tab2:
659
  st.subheader("Game Settings")
660
+ penalty_threshold_input = st.number_input("Penalty Threshold (Timeouts)", min_value=1, value=st.session_state.penalty_threshold, key="admin_penalty")
661
+ dare_frequency_input = st.number_input("Dare Frequency (every N rounds)", min_value=1, value=st.session_state.dare_frequency, key="admin_dare_freq")
662
+ if st.button("Save Settings", key="admin_save_settings"):
663
+ st.session_state.penalty_threshold = penalty_threshold_input
664
+ st.session_state.dare_frequency = dare_frequency_input
665
+ st.success(f"Settings saved! Penalty Threshold: {penalty_threshold_input}, Dare Frequency: every {dare_frequency_input} rounds.")
666
+
667
+ # --- Main Game Interface ---
668
  tab1, tab2, tab3, tab4 = st.tabs(["๐ŸŽฏ Game Zone", "๐Ÿ† Leaderboard", "๐ŸŽ Redeem Points", "๐ŸŒŸ Achievements"])
669
 
670
  with tab1:
671
+ if st.session_state.registered: # Only show game zone if players are registered
672
  st.header("๐ŸŒŸ Current Round")
673
  col1, col2 = st.columns([3, 2])
674
+ current_registered_players = get_registered_player_names()
675
 
676
  with col1:
677
+ if st.button("โธ๏ธ Pause Game" if not st.session_state.game_paused else "โ–ถ๏ธ Resume Game", key="pause_resume_btn"):
678
  st.session_state.game_paused = not st.session_state.game_paused
679
  if st.session_state.game_paused:
680
  st.warning("Game Paused!")
681
+ st.components.v1.html("<script>window.pauseGamuuTimer();</script>", height=0)
682
  else:
683
  st.success("Game Resumed!")
684
+ st.components.v1.html("<script>window.resumeGamuuTimer();</script>", height=0)
685
+ st.rerun()
686
+
687
 
688
  if not st.session_state.game_paused:
689
+ # Round Timer (5 minutes = 300 seconds) - This is an overall round timer, not per question
690
  if st.session_state.round_start_time is None:
691
  st.session_state.round_start_time = time.time()
692
+
693
+ round_elapsed_time = int(time.time() - st.session_state.round_start_time)
694
+ round_time_limit = 300 # 5 minutes
695
+ round_time_left = round_time_limit - round_elapsed_time
696
+
697
  if round_time_left <= 0:
698
+ st.warning("Round time limit reached! Resetting for a new round.")
699
  st.session_state.current_player = None
700
  st.session_state.question = None
701
+ st.session_state.timer_start = None # Reset question timer
702
+ st.session_state.round_start_time = time.time() # Reset round timer
703
  st.session_state.question_count = 0
704
  st.session_state.current_category_index = 0
705
+ # Potentially add more logic here like ending the game or auto-advancing
706
+ st.rerun()
707
+ else:
708
+ st.write(f"โณ Overall Round Time Left: {round_time_left // 60}m {round_time_left % 60}s")
709
+
710
+
711
+ # Category Cycling Logic
712
+ if st.session_state.available_categories: # Only if there are categories to cycle through
713
+ if st.session_state.question_count >= 9 and len(st.session_state.available_categories) > 1:
714
+ st.session_state.current_category_index = (st.session_state.current_category_index + 1) % len(st.session_state.available_categories)
715
+ st.session_state.question_count = 0
716
+ st.success(f"Switching to category: {st.session_state.available_categories[st.session_state.current_category_index]}")
717
+ st.rerun() # Rerun to reflect new category selection
718
+
719
+ # Ensure current_category_index is valid
720
+ if st.session_state.current_category_index >= len(st.session_state.available_categories):
721
+ st.session_state.current_category_index = 0
722
+
723
+ category_options_for_select = st.session_state.available_categories + ['Random']
724
+ try:
725
+ default_cat_index = category_options_for_select.index(st.session_state.available_categories[st.session_state.current_category_index])
726
+ except (IndexError, ValueError): # If index is bad or category not found (e.g. available_categories empty)
727
+ default_cat_index = len(category_options_for_select) -1 # Default to Random
728
+
729
+ selected_category_name = st.selectbox("Choose Question Category",
730
+ category_options_for_select, index=default_cat_index, key="category_select")
731
+ else:
732
+ selected_category_name = st.selectbox("Choose Question Category", ['Random'], key="category_select_norandom") # Only random if no categories
733
+ st.info("No specific categories selected by players. Defaulting to Random from all questions.")
734
 
 
 
735
 
736
+ spice_level_selection = None
737
+ if selected_category_name == '18+' and '18+' in st.session_state.available_categories:
738
+ spice_level_selection = st.select_slider("๐ŸŒถ๏ธ Spice Level",
739
+ options=['Spicy', 'Extra Spicy', 'Space Spicy', 'Hardcore'], key="spice_slider")
740
 
741
+ if st.button("๐ŸŒ€ Spin the Bottle", use_container_width=True, key="spin_bottle_btn", disabled=len(current_registered_players) < 1):
742
+ if current_registered_players: # Ensure there are players
743
+ st.session_state.current_player = random.choice(current_registered_players)
744
+ st.session_state.timer_start = time.time() # Start of question timer
745
  st.session_state.round += 1
746
  st.session_state.question_count += 1
747
+ # Update timer key to force JS timer reset
748
+ st.session_state.timer_instance_key = f"timer_q_{st.session_state.round}_{st.session_state.current_player}"
749
 
750
+
751
+ potential_questions = []
752
+ chosen_category_for_question = selected_category_name
753
+
754
+ # Dare logic
755
+ is_dare_round = (st.session_state.round % st.session_state.dare_frequency == 0 and
756
+ 'Dare' in st.session_state.available_categories and
757
+ st.session_state.questions.get('Dare'))
758
+ if is_dare_round:
759
+ potential_questions = st.session_state.questions['Dare']
760
+ chosen_category_for_question = "Dare"
761
  else:
762
+ if selected_category_name == '18+' and spice_level_selection:
763
+ if '18+' in st.session_state.questions and \
764
+ spice_level_selection in st.session_state.questions['18+']:
765
+ potential_questions = st.session_state.questions['18+'][spice_level_selection]
766
+ else:
767
+ st.warning(f"No questions found for 18+ {spice_level_selection}. Check admin setup.")
768
+ elif selected_category_name == 'Random':
769
+ all_q_lists = []
770
+ for cat_key, cat_value in st.session_state.questions.items():
771
+ if isinstance(cat_value, list) and cat_value:
772
+ all_q_lists.append(cat_value)
773
+ elif isinstance(cat_value, dict): # e.g., 18+
774
+ for sub_cat_list in cat_value.values():
775
+ if sub_cat_list:
776
+ all_q_lists.append(sub_cat_list)
777
+ if all_q_lists:
778
+ random_list_choice = random.choice(all_q_lists)
779
+ potential_questions = random_list_choice
780
+ # Finding the name of the randomly chosen category is complex, skip for now for question text
781
+ chosen_category_for_question = "Random Mix"
782
+ else:
783
+ st.warning("No questions available in any category for Random selection.")
784
+ elif selected_category_name in st.session_state.questions and st.session_state.questions[selected_category_name]:
785
+ potential_questions = st.session_state.questions[selected_category_name]
786
+ else:
787
+ st.warning(f"No questions found for category '{selected_category_name}'.")
788
 
789
+ if potential_questions:
790
+ st.session_state.question = random.choice(potential_questions)
791
+ st.success(f"๐ŸŽฏ {st.session_state.current_player}, it's your turn! ({chosen_category_for_question})")
792
+ else:
793
+ st.session_state.question = "Oops! No questions found for this selection. Admin might need to add some or check category choices."
794
+ st.session_state.current_player = None # Can't proceed
795
+ st.session_state.timer_start = None
796
+ st.rerun()
797
 
 
 
798
  else:
799
  st.error("Not enough players to spin the bottle!")
800
+ else: # Game Paused
801
+ st.info("Game is paused. Press Resume to continue.")
802
 
803
+
804
+ # --- Display Current Player, Question, Timer (col2) ---
805
  if st.session_state.current_player and st.session_state.question and not st.session_state.game_paused:
806
  with col2:
807
+ player_details = st.session_state.players.get(st.session_state.current_player)
808
+ if player_details and player_details.get('avatar'):
809
+ try:
810
+ st.image(f"data:image/png;base64,{player_details['avatar']}", width=100, caption=st.session_state.current_player)
811
+ except Exception as e:
812
+ st.caption(f"{st.session_state.current_player} (avatar error)")
813
+ else:
814
+ st.subheader(f"๐ŸŽฏ Current Player: {st.session_state.current_player}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
 
816
+ st.subheader("โ“ Your Question:")
817
+ st.markdown(f"**{st.session_state.question}**")
818
+
819
+ # Non-blocking Timer - Python logic for timeout is primary
820
+ st.components.v1.html(timer_html, height=80, key=st.session_state.timer_instance_key) # Unique key forces re-render
821
+
822
+ if st.session_state.timer_start and (time.time() - st.session_state.timer_start >= 30):
823
+ st.error("โฐ Time's up! Points deducted.")
824
+ st.session_state.points[st.session_state.current_player] = st.session_state.points.get(st.session_state.current_player, 0) - 10
825
+ st.session_state.timeouts[st.session_state.current_player] = st.session_state.timeouts.get(st.session_state.current_player, 0) + 1
826
+ st.session_state.timer_start = None # Reset timer for next turn logic
827
+ st.session_state.question = None # Clear question
828
+ # Check for penalty after timeout
829
+ current_player_timeouts = st.session_state.timeouts.get(st.session_state.current_player, 0)
830
+ if current_player_timeouts >= st.session_state.penalty_threshold:
831
+ st.warning(f"{st.session_state.current_player} has timed out {current_player_timeouts} times and is penalized!")
832
+ # Penalty logic will be handled below, rerun to show it
833
+ st.session_state.current_player = None # End turn
834
+ st.rerun()
835
+
836
+ # Penalty System (if current_player was set and timed out previously, or just timed out now)
837
+ # This needs to be accessible even if the question timed out, so check before the "Submit Answer" button
838
+ penalized_player = None
839
+ for p_name, t_outs in st.session_state.timeouts.items():
840
+ if t_outs >= st.session_state.penalty_threshold:
841
+ penalized_player = p_name
842
+ break # Handle one penalty at a time for simplicity
843
+
844
+ if penalized_player and penalized_player == st.session_state.current_player: # Only if it's the current player's turn for this penalty UI
845
+ st.error(f"๐Ÿšจ PENALTY! {penalized_player} has timed out too many times.")
846
+ eligible_saviors = [p for p in current_registered_players if p != penalized_player]
847
+ if eligible_saviors:
848
+ savior_name = st.selectbox(f"Choose a Savior for {penalized_player}", eligible_saviors, key=f"savior_{penalized_player}")
849
+ hardcore_questions = st.session_state.questions.get('18+', {}).get('Hardcore', [])
850
+ if hardcore_questions:
851
+ ultimate_question_text = random.choice(hardcore_questions)
852
+ st.write(f"๐Ÿฆธ {savior_name}, answer this ultimate question to save {penalized_player}:")
853
+ st.write(f"**{ultimate_question_text}**")
854
+ if st.button(f"Submit Savior Answer for {penalized_player}", key=f"savior_submit_{penalized_player}"):
855
+ st.success(f"{savior_name} has saved {penalized_player}! Penalty reset.")
856
+ st.session_state.timeouts[penalized_player] = 0
857
+ # Award points to savior?
858
+ st.session_state.points[savior_name] = st.session_state.points.get(savior_name,0) + 20
859
+ st.session_state.current_player = None # End penalized player's effective turn
860
+ st.session_state.question = None
861
+ st.rerun()
862
+ else:
863
+ st.warning("No 'Hardcore' questions available for savior task. Penalty waived for now.")
864
+ st.session_state.timeouts[penalized_player] = 0
865
+ st.rerun()
866
+ else:
867
+ st.warning(f"No saviors available for {penalized_player}. Penalty remains, but consider waiving.")
868
+ # Decide: st.session_state.timeouts[penalized_player] = 0 # or keep penalty
869
+
870
+ # Only show submit if not penalized or penalty handled
871
+ if st.session_state.current_player and st.session_state.question and not (penalized_player and penalized_player == st.session_state.current_player) :
872
+ if st.button("โœ… Submit Answer", key="answer_submit_btn"):
873
+ if st.session_state.timer_start: # Check if timer was actually running
874
+ time_taken = int(time.time() - st.session_state.timer_start)
875
+ points_earned = max(50 - time_taken, 10) # Min 10 points
876
+ else: # Should not happen if button is visible, but safeguard
877
+ points_earned = 10
878
+ st.warning("Timer was not active, default points awarded.")
879
+
880
+ st.session_state.points[st.session_state.current_player] += points_earned
881
+
882
+ player_team = st.session_state.players[st.session_state.current_player]['team']
883
+ # Add points to actual multi-player teams
884
+ if player_team in st.session_state.teams: # st.session_state.teams stores names of multi-player teams
885
+ st.session_state.team_points[player_team] = st.session_state.team_points.get(player_team, 0) + points_earned
886
+
887
+ st.success(f"๐ŸŽ‰ Answer accepted, {st.session_state.current_player}! +{points_earned} points")
888
+ st.session_state.current_player = None
889
+ st.session_state.question = None
890
+ st.session_state.timer_start = None
891
+ st.rerun()
892
+
893
+ # Suggest a New Question (always visible in game zone if players are registered)
894
  with st.expander("๐Ÿ’ก Suggest a New Question"):
895
+ with st.form("question_suggestion_form"):
896
+ sugg_player_name = st.selectbox("Suggesting as:", get_registered_player_names(), key="sugg_player", help="Select your player name.")
897
+ sugg_new_category = st.selectbox("Category", list(st.session_state.questions.keys()), key="sugg_cat")
898
+ sugg_new_question = st.text_input("Your Question Suggestion", key="sugg_q_text")
899
+ if st.form_submit_button("Suggest Question"):
900
+ if sugg_player_name and sugg_new_question and sugg_new_category:
901
+ st.session_state.suggestions.append({'player': sugg_player_name, 'category': sugg_new_category, 'question': sugg_new_question, 'time': datetime.now()})
902
+ st.session_state.points[sugg_player_name] = st.session_state.points.get(sugg_player_name, 0) + 15
903
+ st.success(f"Question suggestion submitted by {sugg_player_name}! +15 points")
904
+ st.rerun()
905
  else:
906
+ st.error("Please select your name, category, and write a question to suggest.")
907
+ else:
908
+ st.info("๐Ÿ‘‹ Welcome to Gamuu! Please register players in the sidebar to start the game.")
909
+
910
 
911
+ with tab2: # Leaderboard
912
  st.header("๐Ÿ… Player Leaderboard")
913
+ if st.session_state.points:
914
+ sorted_players_points = sorted(st.session_state.points.items(), key=lambda x: x[1], reverse=True)
915
+ for i, (p_name, p_points) in enumerate(sorted_players_points):
916
+ player_data = st.session_state.players.get(p_name)
917
+ cols = st.columns([1,4])
918
+ with cols[0]:
919
+ if player_data and player_data.get('avatar'):
920
+ try:
921
+ st.image(f"data:image/png;base64,{player_data['avatar']}", width=50)
922
+ except: pass # Ignore avatar display error
923
+ with cols[1]:
924
+ st.metric(f"{'๐Ÿฅ‡' if i==0 else '๐Ÿฅˆ' if i==1 else '๐Ÿฅ‰' if i==2 else f'{i+1}.'} {p_name}", f"{p_points} points")
925
+ else:
926
+ st.info("No player points recorded yet.")
927
+
928
+ st.header("๐Ÿ… Team Leaderboard (Multi-player Teams)")
929
+ if st.session_state.team_points: # Only show if there are actual multi-player teams with points
930
+ # Filter for actual teams, not solo players who are their own "team" unless explicitly handled
931
+ actual_teams_points = {team_name: pts for team_name, pts in st.session_state.team_points.items() if team_name in st.session_state.teams}
932
+ if actual_teams_points:
933
+ sorted_teams_display = sorted(actual_teams_points.items(), key=lambda x: x[1], reverse=True)
934
+ for i, (t_name, t_points) in enumerate(sorted_teams_display):
935
+ st.metric(f"{'๐Ÿฅ‡' if i==0 else '๐Ÿฅˆ' if i==1 else '๐Ÿฅ‰' if i==2 else f'{i+1}.'} Team {t_name}", f"{t_points} points")
936
+ else:
937
+ st.info("No multi-player team points recorded yet.")
938
+ else:
939
+ st.info("No team points recorded yet.")
940
+
941
+
942
+ st.header("๐Ÿ“œ Leaderboard History")
943
+ if st.button("Save Current Leaderboard to History", key="save_leaderboard_btn"):
944
+ if st.session_state.points or st.session_state.team_points:
945
+ history_entry = {
946
+ 'date': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
947
+ 'players': dict(st.session_state.points), # Save a copy
948
+ 'teams': dict(st.session_state.team_points) # Save a copy
949
+ }
950
+ st.session_state.leaderboard_history.append(history_entry)
951
+ st.success("Leaderboard snapshot saved!")
952
+ else:
953
+ st.warning("Nothing to save (no points recorded).")
954
+
955
+ if st.session_state.leaderboard_history:
956
+ for entry in reversed(st.session_state.leaderboard_history): # Show newest first
957
+ with st.expander(f"Snapshot from: {entry['date']}"):
958
+ st.write("**Players:**", entry['players'])
959
+ st.write("**Teams:**", entry['teams'])
960
+ else:
961
+ st.info("No leaderboard history saved yet.")
962
+
963
+
964
+ with tab3: # Redeem Points
965
  st.header("๐Ÿ›๏ธ Redeem Points")
966
+ redeeming_player = st.selectbox("Select Player for Redemption:", get_registered_player_names(), key="redeem_player_select", help="Choose who is redeeming points.")
967
+
968
+ if redeeming_player:
969
+ current_player_points = st.session_state.points.get(redeeming_player, 0)
970
+ st.write(f"**{redeeming_player}** has **{current_player_points}** points available.")
971
+
972
+ rewards = {
973
+ "Basic": [("Custom Emoji ๐ŸŽจ", 50, "emoji_reward"), ("Nickname Color Change ๐ŸŒˆ", 75, "color_reward")],
974
+ "Premium": [("Special Badge โœจ", 200, "badge_reward"), ("Unlock 'Dare Devil' Mode (more dares) ๐Ÿ˜ˆ", 250, "dare_mode_reward")],
975
+ "Spicy": [("18+ Question Pack ๐Ÿ”ฅ", 300, "spicy_pack_reward"), ("'Ultimate Truth' Question for someone else ๐Ÿ’ฃ", 400, "ultimate_truth_reward")]
976
+ }
977
 
978
+ cols = st.columns(len(rewards))
979
+ for i, (tier_name, tier_rewards) in enumerate(rewards.items()):
980
+ with cols[i]:
981
+ st.subheader(f"๐ŸŽ {tier_name} Rewards")
982
+ for reward_name, cost, reward_key_suffix in tier_rewards:
983
+ if st.button(f"{reward_name} ({cost} points)", key=f"redeem_{reward_key_suffix}_{redeeming_player}", disabled=current_player_points < cost):
984
+ if current_player_points >= cost:
985
+ st.session_state.points[redeeming_player] -= cost
986
+ # Add achievement logic or actual reward effect here
987
+ if redeeming_player not in st.session_state.achievements:
988
+ st.session_state.achievements[redeeming_player] = []
989
+ achievement_text = f"Redeemed '{reward_name}'"
990
+ if achievement_text not in st.session_state.achievements[redeeming_player]:
991
+ st.session_state.achievements[redeeming_player].append(achievement_text)
992
+ st.success(f"๐ŸŽ‰ {reward_name} redeemed by {redeeming_player}!")
993
+ st.rerun() # Update points display and button states
994
+ # else: # Button should be disabled, but as a fallback
995
+ # st.error("Not enough points! (Button should have been disabled)")
 
996
  else:
997
+ st.info("Please register players and select a player to see redeemable rewards.")
998
 
999
+ with tab4: # Achievements
1000
  st.header("๐ŸŒŸ Achievements")
1001
+ achievements_player = st.selectbox("View Achievements For:", get_registered_player_names(), key="achievements_player_select", help="Select player to view their achievements.")
1002
+
1003
+ if achievements_player:
1004
+ st.write(f"๐Ÿ† **{achievements_player}'s Achievements:**")
1005
+ player_achievements = st.session_state.achievements.get(achievements_player, [])
1006
+ if player_achievements:
1007
+ for ach in player_achievements:
1008
+ st.markdown(f"- {ach}")
1009
  else:
1010
+ st.write("No achievements unlocked yet. Keep playing to earn them!")
1011
  else:
1012
+ st.info("Please register players and select a player to view achievements.")
1013
 
1014
+ # --- Footer ---
1015
  st.markdown("---")
1016
+ st.markdown("Made with โค๏ธ for **Gamuu** - v3.1 โ‹… [Privacy Policy] โ‹… [Terms of Service]")
1017
+ st.caption("Note: 'Ultimate Truth' and 'Dare Devil Mode' are conceptual and not fully implemented in game logic yet.")