Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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'
|
| 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
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
}
|
| 498 |
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
</script>
|
| 503 |
<div id="timer">โณ Time Remaining: 30s</div>
|
| 504 |
-
<progress id="progress" value="0" max="30"></progress>
|
| 505 |
"""
|
| 506 |
|
| 507 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
with st.sidebar:
|
| 509 |
st.header("๐ Player Registration")
|
| 510 |
-
with st.form("
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
'
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 548 |
if st.form_submit_button("Submit Feedback"):
|
| 549 |
-
if
|
| 550 |
-
st.session_state.feedback.append(feedback)
|
| 551 |
-
st.session_state.points[
|
| 552 |
-
st.success("Thanks for your feedback! +5 points")
|
|
|
|
|
|
|
|
|
|
| 553 |
else:
|
| 554 |
-
st.error("Please
|
|
|
|
| 555 |
|
| 556 |
st.header("๐ Admin Login")
|
| 557 |
-
|
| 558 |
-
if st.button("Login as Admin"):
|
| 559 |
-
if
|
| 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("
|
| 573 |
-
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
if st.form_submit_button("Add Question"):
|
| 576 |
-
if
|
| 577 |
-
if
|
| 578 |
-
|
| 579 |
-
|
|
|
|
| 580 |
else:
|
| 581 |
-
|
| 582 |
st.success("Question added successfully!")
|
| 583 |
else:
|
| 584 |
-
st.error("
|
| 585 |
|
| 586 |
st.subheader("Remove Question")
|
| 587 |
-
with st.form("
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
st.session_state.questions[
|
| 599 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
|
| 601 |
with admin_tab2:
|
| 602 |
st.subheader("Game Settings")
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
if st.button("Save Settings"):
|
| 606 |
-
st.session_state.penalty_threshold =
|
| 607 |
-
st.session_state.dare_frequency =
|
| 608 |
-
st.success(f"Settings saved! Penalty Threshold: {
|
| 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 |
-
|
| 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>
|
| 625 |
else:
|
| 626 |
st.success("Game Resumed!")
|
| 627 |
-
st.components.v1.html("<script>
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
st.session_state.question_count =
|
| 650 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
|
| 652 |
-
selected_category = st.selectbox("Choose Question Category",
|
| 653 |
-
available_categories + ['Random'], index=st.session_state.current_category_index)
|
| 654 |
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
|
|
|
| 658 |
|
| 659 |
-
if st.button("๐ Spin the Bottle", use_container_width=True):
|
| 660 |
-
if
|
| 661 |
-
st.session_state.current_player = random.choice(
|
| 662 |
-
st.session_state.timer_start = time.time()
|
| 663 |
st.session_state.round += 1
|
| 664 |
st.session_state.question_count += 1
|
|
|
|
|
|
|
| 665 |
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
|
|
|
|
|
|
| 675 |
else:
|
| 676 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
-
if
|
| 679 |
-
st.session_state.question = random.choice(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 689 |
-
if
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 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("
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
st.session_state.
|
| 734 |
-
st.
|
|
|
|
|
|
|
| 735 |
else:
|
| 736 |
-
st.error("Please
|
|
|
|
|
|
|
|
|
|
| 737 |
|
| 738 |
-
with tab2:
|
| 739 |
st.header("๐
Player Leaderboard")
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
st.
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
}
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
st.header("๐๏ธ Redeem Points")
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
st.
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
st.success("๐ Pack unlocked!")
|
| 791 |
else:
|
| 792 |
-
st.
|
| 793 |
|
| 794 |
-
with tab4:
|
| 795 |
st.header("๐ Achievements")
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
| 801 |
else:
|
| 802 |
-
st.write("No achievements yet. Keep playing!")
|
| 803 |
else:
|
| 804 |
-
st.
|
| 805 |
|
| 806 |
-
# Footer
|
| 807 |
st.markdown("---")
|
| 808 |
-
st.markdown("**
|
|
|
|
|
|
| 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.")
|