Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -400,21 +400,23 @@ def unified_push_message(game_id, msg_obj):
|
|
| 400 |
# ---------------------------
|
| 401 |
def unified_push_leaderboard(row):
|
| 402 |
"""
|
| 403 |
-
Push a row to leaderboard
|
| 404 |
-
and automatically adds a UTC timestamp.
|
| 405 |
"""
|
| 406 |
-
row = row.copy()
|
| 407 |
-
|
| 408 |
-
|
|
|
|
| 409 |
|
| 410 |
mode = st.session_state.get("mode_selection", "Offline")
|
| 411 |
|
| 412 |
if mode == "Online":
|
| 413 |
ok, _ = init_firebase_if_needed()
|
| 414 |
-
if ok:
|
| 415 |
-
|
|
|
|
|
|
|
| 416 |
|
| 417 |
-
#
|
| 418 |
try:
|
| 419 |
df = pd.read_csv(LEADERBOARD_FILE)
|
| 420 |
except FileNotFoundError:
|
|
@@ -423,6 +425,30 @@ def unified_push_leaderboard(row):
|
|
| 423 |
df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
|
| 424 |
df.to_csv(LEADERBOARD_FILE, index=False)
|
| 425 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
# ---------------------------
|
| 427 |
# Get weekly leaderboard
|
| 428 |
# ---------------------------
|
|
@@ -828,86 +854,82 @@ def get_top3_and_player_count(game_id):
|
|
| 828 |
# Home page
|
| 829 |
|
| 830 |
HEARTBEAT_THRESHOLD_SECONDS = 60 # adjust if needed
|
| 831 |
-
|
| 832 |
def home_page():
|
| 833 |
st.header("Home")
|
| 834 |
st.write("Create games, invite friends, play and climb the leaderboard.")
|
| 835 |
|
| 836 |
# ---------------- TOTAL ONLINE PLAYERS ----------------
|
| 837 |
-
|
| 838 |
-
total_online = sum(len(v) for v in players_map.values())
|
| 839 |
st.metric("๐ข Players Online", total_online)
|
| 840 |
|
| 841 |
# ---------------- SHOW LAST SCORE ----------------
|
| 842 |
last_score = st.session_state.get('last_score')
|
| 843 |
last_game = st.session_state.get('last_game')
|
| 844 |
-
if last_score is not None:
|
| 845 |
st.success(f"Your last score: {last_score} (Game {last_game})")
|
| 846 |
|
| 847 |
# ---------------- LOAD GAMES ----------------
|
| 848 |
games = unified_get("games") or {}
|
| 849 |
-
|
|
|
|
|
|
|
| 850 |
|
|
|
|
| 851 |
for g in sorted(games.values(), key=lambda x: x.get("created_at", ""), reverse=True)[:10]:
|
| 852 |
gid = g.get("game_id")
|
| 853 |
-
|
| 854 |
-
# Use players/submissions stored in game dict
|
| 855 |
-
players = g.get("players", [])
|
| 856 |
-
submissions = g.get("submissions", {})
|
| 857 |
-
|
| 858 |
st.markdown(f"### ๐ฎ Game: **{gid}** {'(Closed)' if g.get('closed') else ''}")
|
| 859 |
st.write(f"Host: {g.get('host')} โ Topics: {', '.join(g.get('topics', []))}")
|
| 860 |
st.write(f"Created: {g.get('created_at')}")
|
| 861 |
|
| 862 |
-
|
| 863 |
-
|
|
|
|
| 864 |
|
| 865 |
-
st.write(f"Players joined: **{
|
| 866 |
-
st.write(f"Submitted: **{
|
| 867 |
|
| 868 |
-
#
|
| 869 |
if submissions:
|
| 870 |
st.markdown("๐ **Top 3 Players**")
|
| 871 |
top_players = sorted(submissions.items(), key=lambda x: x[1], reverse=True)[:3]
|
| 872 |
-
for i, (
|
| 873 |
-
st.write(f"{i}. ๐ฎ **{
|
| 874 |
|
| 875 |
-
#
|
| 876 |
if players:
|
| 877 |
st.markdown("**๐ฅ Player Status**")
|
| 878 |
-
for
|
| 879 |
-
if
|
| 880 |
-
st.write(f"๐ฎ **{
|
| 881 |
else:
|
| 882 |
-
st.write(f"๐ฎ **{
|
| 883 |
|
| 884 |
st.markdown("---")
|
| 885 |
|
| 886 |
-
#
|
| 887 |
-
if not g.get("closed"):
|
| 888 |
st.info(f"Share this Game ID: {gid}")
|
| 889 |
render_copy_button(gid, gid)
|
| 890 |
|
| 891 |
-
if st.
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
st.experimental_rerun()
|
| 911 |
|
| 912 |
# ---------------- WEEKLY LEADERBOARD ----------------
|
| 913 |
st.subheader("๐ Weekly Leaderboard (Top 10)")
|
|
@@ -918,87 +940,25 @@ def home_page():
|
|
| 918 |
for i, r in enumerate(weekly, 1):
|
| 919 |
st.write(f"{i}. {r.get('avatar','๐ฎ')} **{r['name']}** (Game {r['game_id']}) โ {r['score']} pts")
|
| 920 |
|
| 921 |
-
# ----------------
|
| 922 |
-
if 'active_game' not in st.session_state:
|
| 923 |
-
if games:
|
| 924 |
-
st.session_state['active_game'] = list(games.keys())[0]
|
| 925 |
-
else:
|
| 926 |
-
st.warning("No games exist yet. Please create a game first.")
|
| 927 |
-
|
| 928 |
-
if 'username' not in st.session_state:
|
| 929 |
-
st.session_state['username'] = "Guest"
|
| 930 |
-
|
| 931 |
-
# Safely update player submissions if a game is active
|
| 932 |
active_game_id = st.session_state.get('active_game')
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
|
| 941 |
-
|
| 942 |
-
|
|
|
|
| 943 |
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
|
| 948 |
-
def create_game(host=None, topics=[], num_questions=5, auto_close=True, ai_topic=None):
|
| 949 |
-
games = unified_get("games") or {}
|
| 950 |
-
gid = f"GAME{int(time.time())}"
|
| 951 |
-
host = host or st.session_state.get("username", "Host")
|
| 952 |
-
|
| 953 |
-
questions = []
|
| 954 |
-
|
| 955 |
-
# 1๏ธโฃ AI questions (already dict format)
|
| 956 |
-
if ai_topic:
|
| 957 |
-
ai_questions = generate_ai_questions(ai_topic, num_questions=num_questions)
|
| 958 |
-
if ai_questions:
|
| 959 |
-
for q in ai_questions:
|
| 960 |
-
questions.append({
|
| 961 |
-
"question": q.get("question", ""),
|
| 962 |
-
"options": q.get("options", []),
|
| 963 |
-
"answer": q.get("answer", "")
|
| 964 |
-
})
|
| 965 |
|
| 966 |
-
# 2๏ธโฃ Static fallback (tuple โ dict conversion)
|
| 967 |
-
if not questions:
|
| 968 |
-
for topic in topics:
|
| 969 |
-
qs = questions_db.get(topic, [])
|
| 970 |
-
for q in qs[:num_questions]:
|
| 971 |
-
questions.append({
|
| 972 |
-
"question": q[0],
|
| 973 |
-
"options": q[1],
|
| 974 |
-
"answer": q[2]
|
| 975 |
-
})
|
| 976 |
-
|
| 977 |
-
# ๐น Store game
|
| 978 |
-
games[gid] = {
|
| 979 |
-
"game_id": gid,
|
| 980 |
-
"host": host,
|
| 981 |
-
"topics": topics,
|
| 982 |
-
"questions": questions,
|
| 983 |
-
"created_at": now_iso(),
|
| 984 |
-
"closed": False,
|
| 985 |
-
"auto_close": auto_close
|
| 986 |
-
}
|
| 987 |
-
|
| 988 |
-
unified_set("games", games)
|
| 989 |
-
|
| 990 |
-
# โ
REQUIRED: prepare play state
|
| 991 |
-
st.session_state['game_id'] = gid
|
| 992 |
-
st.session_state['active_game_id'] = gid
|
| 993 |
-
st.session_state['game_questions'] = questions
|
| 994 |
-
st.session_state['current_index'] = 0
|
| 995 |
-
st.session_state['answers'] = [""] * len(questions)
|
| 996 |
-
st.session_state['answer_times'] = [None] * len(questions)
|
| 997 |
-
st.session_state['question_started_at'] = None
|
| 998 |
-
|
| 999 |
-
st.success(f"Game created: {gid} with {len(questions)} questions.")
|
| 1000 |
-
return gid
|
| 1001 |
-
|
| 1002 |
# -------------------------
|
| 1003 |
# PLAY PAGE
|
| 1004 |
# -------------------------
|
|
|
|
| 400 |
# ---------------------------
|
| 401 |
def unified_push_leaderboard(row):
|
| 402 |
"""
|
| 403 |
+
Push a row to leaderboard per game_id in Firebase and also local CSV.
|
|
|
|
| 404 |
"""
|
| 405 |
+
row = row.copy()
|
| 406 |
+
row["timestamp"] = datetime.utcnow().isoformat() + "Z"
|
| 407 |
+
game_id = row.get("game_id")
|
| 408 |
+
username = row.get("name")
|
| 409 |
|
| 410 |
mode = st.session_state.get("mode_selection", "Offline")
|
| 411 |
|
| 412 |
if mode == "Online":
|
| 413 |
ok, _ = init_firebase_if_needed()
|
| 414 |
+
if ok and game_id and username:
|
| 415 |
+
# Push under game_id
|
| 416 |
+
ref = db.reference(f"/games/{game_id}/scores/{username}")
|
| 417 |
+
ref.set(row)
|
| 418 |
|
| 419 |
+
# Local CSV backup
|
| 420 |
try:
|
| 421 |
df = pd.read_csv(LEADERBOARD_FILE)
|
| 422 |
except FileNotFoundError:
|
|
|
|
| 425 |
df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
|
| 426 |
df.to_csv(LEADERBOARD_FILE, index=False)
|
| 427 |
|
| 428 |
+
def show_game_leaderboard(game_id):
|
| 429 |
+
st.subheader(f"Leaderboard for Game {game_id}")
|
| 430 |
+
|
| 431 |
+
mode = st.session_state.get("mode_selection", "Offline")
|
| 432 |
+
|
| 433 |
+
rows = []
|
| 434 |
+
if mode == "Online":
|
| 435 |
+
ok, _ = init_firebase_if_needed()
|
| 436 |
+
if ok:
|
| 437 |
+
rows_dict = fb_get(f"/games/{game_id}/scores") or {}
|
| 438 |
+
rows = list(rows_dict.values())
|
| 439 |
+
else:
|
| 440 |
+
df = pd.read_csv(LEADERBOARD_FILE)
|
| 441 |
+
rows = df[df["game_id"]==game_id].to_dict(orient="records")
|
| 442 |
+
|
| 443 |
+
if not rows:
|
| 444 |
+
st.info("No scores yet for this game.")
|
| 445 |
+
return
|
| 446 |
+
|
| 447 |
+
rows.sort(key=lambda x: x.get("score",0), reverse=True)
|
| 448 |
+
|
| 449 |
+
for i, r in enumerate(rows, 1):
|
| 450 |
+
st.write(f"{i}. {r.get('avatar','๐ฎ')} **{r['name']}** โ {r.get('score')} pts")
|
| 451 |
+
|
| 452 |
# ---------------------------
|
| 453 |
# Get weekly leaderboard
|
| 454 |
# ---------------------------
|
|
|
|
| 854 |
# Home page
|
| 855 |
|
| 856 |
HEARTBEAT_THRESHOLD_SECONDS = 60 # adjust if needed
|
|
|
|
| 857 |
def home_page():
|
| 858 |
st.header("Home")
|
| 859 |
st.write("Create games, invite friends, play and climb the leaderboard.")
|
| 860 |
|
| 861 |
# ---------------- TOTAL ONLINE PLAYERS ----------------
|
| 862 |
+
total_online = get_total_online_players()
|
|
|
|
| 863 |
st.metric("๐ข Players Online", total_online)
|
| 864 |
|
| 865 |
# ---------------- SHOW LAST SCORE ----------------
|
| 866 |
last_score = st.session_state.get('last_score')
|
| 867 |
last_game = st.session_state.get('last_game')
|
| 868 |
+
if last_score is not None and last_game:
|
| 869 |
st.success(f"Your last score: {last_score} (Game {last_game})")
|
| 870 |
|
| 871 |
# ---------------- LOAD GAMES ----------------
|
| 872 |
games = unified_get("games") or {}
|
| 873 |
+
if not games:
|
| 874 |
+
st.info("No games exist yet. Please create a game first.")
|
| 875 |
+
return
|
| 876 |
|
| 877 |
+
st.subheader("Recent Games")
|
| 878 |
for g in sorted(games.values(), key=lambda x: x.get("created_at", ""), reverse=True)[:10]:
|
| 879 |
gid = g.get("game_id")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
st.markdown(f"### ๐ฎ Game: **{gid}** {'(Closed)' if g.get('closed') else ''}")
|
| 881 |
st.write(f"Host: {g.get('host')} โ Topics: {', '.join(g.get('topics', []))}")
|
| 882 |
st.write(f"Created: {g.get('created_at')}")
|
| 883 |
|
| 884 |
+
# Players & submissions
|
| 885 |
+
players = g.get("players", [])
|
| 886 |
+
submissions = g.get("submissions", {})
|
| 887 |
|
| 888 |
+
st.write(f"Players joined: **{len(players)}**")
|
| 889 |
+
st.write(f"Submitted: **{len(submissions)} / {len(players)}**")
|
| 890 |
|
| 891 |
+
# Top 3 players
|
| 892 |
if submissions:
|
| 893 |
st.markdown("๐ **Top 3 Players**")
|
| 894 |
top_players = sorted(submissions.items(), key=lambda x: x[1], reverse=True)[:3]
|
| 895 |
+
for i, (uname, score) in enumerate(top_players, start=1):
|
| 896 |
+
st.write(f"{i}. ๐ฎ **{uname}** โ {score} pts")
|
| 897 |
|
| 898 |
+
# Player status
|
| 899 |
if players:
|
| 900 |
st.markdown("**๐ฅ Player Status**")
|
| 901 |
+
for uname in players:
|
| 902 |
+
if uname in submissions:
|
| 903 |
+
st.write(f"๐ฎ **{uname}** โ โ
Submitted ({submissions[uname]} pts)")
|
| 904 |
else:
|
| 905 |
+
st.write(f"๐ฎ **{uname}** โ โณ Playing")
|
| 906 |
|
| 907 |
st.markdown("---")
|
| 908 |
|
| 909 |
+
# Invite & challenge friends
|
| 910 |
+
if not g.get("closed") and st.session_state.get("username"):
|
| 911 |
st.info(f"Share this Game ID: {gid}")
|
| 912 |
render_copy_button(gid, gid)
|
| 913 |
|
| 914 |
+
if st.button(f"Invite friends to {gid}", key=f"invite_{gid}"):
|
| 915 |
+
friends = get_friends_map().get(st.session_state["username"], [])
|
| 916 |
+
if not friends:
|
| 917 |
+
st.warning("No friends to invite.")
|
| 918 |
+
else:
|
| 919 |
+
for f in friends:
|
| 920 |
+
send_game_invite(st.session_state["username"], f, gid)
|
| 921 |
+
st.success("Invites sent to friends.")
|
| 922 |
+
|
| 923 |
+
if st.button(f"Challenge friends with a new game like {gid}", key=f"challenge_{gid}"):
|
| 924 |
+
new_id = create_game(
|
| 925 |
+
host=st.session_state.get("username", "Host"),
|
| 926 |
+
topics=g.get("topics", []),
|
| 927 |
+
num_questions=len(g.get("questions", []))
|
| 928 |
+
)
|
| 929 |
+
st.success(f"Challenge created: {new_id}")
|
| 930 |
+
st.session_state['active_game_id'] = new_id
|
| 931 |
+
st.session_state['game_questions'] = games.get(new_id, {}).get("questions", [])
|
| 932 |
+
st.experimental_rerun()
|
|
|
|
| 933 |
|
| 934 |
# ---------------- WEEKLY LEADERBOARD ----------------
|
| 935 |
st.subheader("๐ Weekly Leaderboard (Top 10)")
|
|
|
|
| 940 |
for i, r in enumerate(weekly, 1):
|
| 941 |
st.write(f"{i}. {r.get('avatar','๐ฎ')} **{r['name']}** (Game {r['game_id']}) โ {r['score']} pts")
|
| 942 |
|
| 943 |
+
# ---------------- UPDATE ACTIVE GAME SUBMISSIONS ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 944 |
active_game_id = st.session_state.get('active_game')
|
| 945 |
+
username = st.session_state.get('username')
|
| 946 |
+
if active_game_id and 'final_score' in st.session_state and username:
|
| 947 |
+
games = unified_get("games") or {}
|
| 948 |
+
game = games.get(active_game_id, {})
|
| 949 |
+
if game:
|
| 950 |
+
players = game.get("players", [])
|
| 951 |
+
submissions = game.get("submissions", {})
|
| 952 |
|
| 953 |
+
if username not in players:
|
| 954 |
+
players.append(username)
|
| 955 |
+
submissions[username] = st.session_state['final_score']
|
| 956 |
|
| 957 |
+
game["players"] = players
|
| 958 |
+
game["submissions"] = submissions
|
| 959 |
+
unified_set("games", {active_game_id: game})
|
| 960 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 961 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 962 |
# -------------------------
|
| 963 |
# PLAY PAGE
|
| 964 |
# -------------------------
|