Files changed (1) hide show
  1. app.py +121 -45
app.py CHANGED
@@ -415,6 +415,45 @@ def unified_push_leaderboard(row):
415
  df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
416
  df.to_csv(LEADERBOARD_FILE, index=False)
417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  # ----------------- Session & Presence helpers -----------------
419
  def now_iso():
420
  return datetime.utcnow().isoformat()
@@ -528,7 +567,16 @@ def compute_winners(game_id):
528
 
529
  return df.head(3).to_dict(orient="records")
530
 
 
 
 
 
 
 
 
 
531
  # ----------------- Create Game Page -----------------
 
532
  def create_game_page():
533
  st.header("Create Game")
534
 
@@ -695,75 +743,88 @@ def home_page():
695
  st.header("Home")
696
  st.write("Create games, invite friends, play and climb the leaderboard.")
697
 
 
 
 
 
 
698
  # Show last score
699
  if st.session_state.get('last_score') is not None:
700
- st.success(f"Your last score: {st.session_state['last_score']} (Game {st.session_state.get('last_game')})")
 
 
 
701
 
702
- # Load games and players
703
  games = unified_get("games") or {}
704
- players_map = unified_get("players") or {}
705
 
706
  st.subheader("Recent games")
 
707
  for g in sorted(games.values(), key=lambda x: x.get("created_at", ""), reverse=True)[:10]:
708
  gid = g.get("game_id")
 
 
709
  st.markdown(f"### ๐ŸŽฎ Game: **{gid}** {'(Closed)' if g.get('closed') else ''}")
710
  st.write(f"Host: {g.get('host')} โ€” Topics: {', '.join(g.get('topics', []))}")
711
  st.write(f"Created: {g.get('created_at')}")
712
 
713
- # Players for this game
714
- players_here = players_map.get(gid, {}) or {}
715
- st.write(f"Players joined: **{len(players_here)}**")
716
-
717
- # Submitted count
718
- submitted_count = sum(1 for p in players_here.values() if p.get("submitted"))
719
- st.write(f"Submitted: **{submitted_count} / {len(players_here)}**")
720
-
721
- # Final scores
722
- if players_here:
723
- st.markdown("**๐Ÿ“Š Final Scores**")
724
- for uname_p, info in players_here.items():
725
- if info.get("submitted"):
726
- st.write(
727
- f"{info.get('avatar','๐ŸŽฎ')} **{uname_p}** โ€” "
728
- f"Score: **{info.get('score', 0)}**, "
729
- f"Percentage: **{info.get('percentage', 0)}%**"
730
- )
 
 
731
 
732
- # Player status
733
  if players_here:
734
  st.markdown("**๐Ÿ‘ฅ Player Status**")
735
  for uname_p, info in players_here.items():
736
- status = "โœ… Submitted" if info.get('submitted') else "โณ Playing"
737
  st.write(f"{info.get('avatar','๐ŸŽฎ')} **{uname_p}** โ€” {status}")
738
 
739
  st.markdown("---")
740
 
741
- # Invite & challenge buttons
742
- if not g.get('closed'):
743
  st.info(f"Share this Game ID: {gid}")
744
  render_copy_button(gid, gid)
745
 
746
- if st.session_state.get('username'):
747
  if st.button(f"Invite your friends to {gid}", key=f"invite_{gid}"):
748
- friends = get_friends_map().get(st.session_state['username'], [])
749
  if not friends:
750
  st.warning("No friends to invite.")
751
  else:
752
  for f in friends:
753
- send_game_invite(st.session_state['username'], f, gid)
754
  st.success("Invites sent to friends.")
755
 
756
  if st.button(f"Challenge friends with a new game like {gid}", key=f"challenge_{gid}"):
757
  new_id = create_game(
758
- st.session_state.get('username','Host'),
759
- g.get('topics', []),
760
- num_questions=len(g.get('questions', []))
761
  )
762
- # Update session for new game
763
- st.session_state["current_game_id"] = new_id
764
  st.session_state["game_questions"] = games.get(new_id, {}).get("questions", [])
765
  st.success(f"Challenge created: {new_id}")
766
- st.experimental_rerun()
 
 
767
 
768
  # Weekly leaderboard
769
  st.subheader("๐Ÿ† Weekly Leaderboard (Top 10)")
@@ -1117,7 +1178,9 @@ def inbox_page():
1117
  def leaderboard_page():
1118
  st.header("Leaderboard")
1119
 
1120
- rows = unified_get("leaderboard") or []
 
 
1121
  if isinstance(rows, list) and rows:
1122
  df = pd.DataFrame(rows)
1123
  else:
@@ -1130,9 +1193,14 @@ def leaderboard_page():
1130
  st.info("No scores yet.")
1131
  return
1132
 
 
 
 
 
1133
  # ๐Ÿ”’ Stable sorting
1134
  sort_cols = ["score"]
1135
  asc = [False]
 
1136
  if "timestamp" in df.columns:
1137
  sort_cols.append("timestamp")
1138
  asc.append(True)
@@ -1146,10 +1214,10 @@ def leaderboard_page():
1146
  st.markdown(f"**๐Ÿ‘ฅ Total Players:** {total_players}")
1147
  st.markdown(f"**๐ŸŽฎ Total Games:** {total_games}")
1148
 
1149
- # ๐Ÿ”ฅ TOP 3 PODIUM
1150
  st.markdown("### ๐Ÿ† Top 3 Overall")
1151
  top3 = df_sorted.head(3)
1152
- for i, row in top3.iterrows():
1153
  st.write(
1154
  f"{row.get('avatar','๐ŸŽฎ')} **{row.get('name','Unknown')}** โ€” "
1155
  f"{row.get('score',0)} pts"
@@ -1163,25 +1231,33 @@ def leaderboard_page():
1163
  # Existing chart (unchanged)
1164
  fig = px.bar(
1165
  df_sorted.head(10),
1166
- x='name',
1167
- y='score'
1168
  )
1169
  st.plotly_chart(fig, use_container_width=True)
1170
 
1171
 
1172
- def get_weekly_leaderboard():
1173
- rows = unified_get("leaderboard") or []
 
1174
  one_week_ago = datetime.utcnow() - timedelta(days=7)
1175
 
1176
  weekly = []
1177
  for r in rows:
1178
- ts = parse_iso(r.get("timestamp"))
1179
- if ts and ts >= one_week_ago:
 
 
 
 
 
 
 
1180
  weekly.append(r)
1181
 
1182
- # Sort by score desc
1183
  weekly.sort(key=lambda x: x.get("score", 0), reverse=True)
1184
- return weekly[:10]
1185
  # Route pages
1186
  if page == "Home":
1187
  home_page()
 
415
  df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
416
  df.to_csv(LEADERBOARD_FILE, index=False)
417
 
418
+
419
+ def get_weekly_leaderboard(limit=10):
420
+ mode = st.session_state.get("mode_selection", "Offline")
421
+ rows = []
422
+
423
+ if mode == "Online":
424
+ ok, _ = init_firebase_if_needed()
425
+ if ok:
426
+ rows = fb_get("/leaderboard") or []
427
+ else:
428
+ try:
429
+ rows = pd.read_csv(LEADERBOARD_FILE).to_dict("records")
430
+ except Exception:
431
+ rows = []
432
+
433
+ if not rows:
434
+ return []
435
+
436
+ # Start of current week (Monday)
437
+ now = datetime.utcnow()
438
+ start_of_week = now - timedelta(days=now.weekday())
439
+
440
+ weekly = []
441
+ for r in rows:
442
+ ts = r.get("timestamp")
443
+ if not ts:
444
+ continue
445
+ try:
446
+ played = datetime.fromisoformat(ts)
447
+ except Exception:
448
+ continue
449
+
450
+ if played >= start_of_week:
451
+ weekly.append(r)
452
+
453
+ weekly.sort(key=lambda x: x.get("score", 0), reverse=True)
454
+ return weekly[:limit]
455
+
456
+
457
  # ----------------- Session & Presence helpers -----------------
458
  def now_iso():
459
  return datetime.utcnow().isoformat()
 
567
 
568
  return df.head(3).to_dict(orient="records")
569
 
570
+ #------Total players--------------------
571
+ def get_total_online_players():
572
+ players = unified_get("players") or {}
573
+ total = 0
574
+ for gid in players:
575
+ total += len(players[gid])
576
+ return total
577
+
578
  # ----------------- Create Game Page -----------------
579
+
580
  def create_game_page():
581
  st.header("Create Game")
582
 
 
743
  st.header("Home")
744
  st.write("Create games, invite friends, play and climb the leaderboard.")
745
 
746
+ # ---------------- TOTAL ONLINE PLAYERS ----------------
747
+ players_map = unified_get("players") or {}
748
+ total_online = sum(len(v) for v in players_map.values())
749
+ st.metric("๐ŸŸข Players Online", total_online)
750
+
751
  # Show last score
752
  if st.session_state.get('last_score') is not None:
753
+ st.success(
754
+ f"Your last score: {st.session_state['last_score']} "
755
+ f"(Game {st.session_state.get('last_game')})"
756
+ )
757
 
758
+ # Load games
759
  games = unified_get("games") or {}
 
760
 
761
  st.subheader("Recent games")
762
+
763
  for g in sorted(games.values(), key=lambda x: x.get("created_at", ""), reverse=True)[:10]:
764
  gid = g.get("game_id")
765
+ players_here = players_map.get(gid, {}) or {}
766
+
767
  st.markdown(f"### ๐ŸŽฎ Game: **{gid}** {'(Closed)' if g.get('closed') else ''}")
768
  st.write(f"Host: {g.get('host')} โ€” Topics: {', '.join(g.get('topics', []))}")
769
  st.write(f"Created: {g.get('created_at')}")
770
 
771
+ # ---------------- GAME STATS ----------------
772
+ joined = len(players_here)
773
+ submitted = sum(1 for p in players_here.values() if p.get("submitted"))
774
+
775
+ st.write(f"Players joined: **{joined}**")
776
+ st.write(f"Submitted: **{submitted} / {joined}**")
777
+
778
+ # ---------------- TOP 3 PLAYERS ----------------
779
+ submitted_players = [
780
+ (u, d) for u, d in players_here.items() if d.get("submitted")
781
+ ]
782
+ submitted_players.sort(key=lambda x: x[1].get("score", 0), reverse=True)
783
+
784
+ if submitted_players:
785
+ st.markdown("๐Ÿ† **Top 3 Players**")
786
+ for i, (uname_p, info) in enumerate(submitted_players[:3], start=1):
787
+ st.write(
788
+ f"{i}. {info.get('avatar','๐ŸŽฎ')} **{uname_p}** โ€” "
789
+ f"{info.get('score',0)} pts ({info.get('percentage',0)}%)"
790
+ )
791
 
792
+ # ---------------- PLAYER STATUS ----------------
793
  if players_here:
794
  st.markdown("**๐Ÿ‘ฅ Player Status**")
795
  for uname_p, info in players_here.items():
796
+ status = "โœ… Submitted" if info.get("submitted") else "โณ Playing"
797
  st.write(f"{info.get('avatar','๐ŸŽฎ')} **{uname_p}** โ€” {status}")
798
 
799
  st.markdown("---")
800
 
801
+ # ---------------- INVITE & CHALLENGE ----------------
802
+ if not g.get("closed"):
803
  st.info(f"Share this Game ID: {gid}")
804
  render_copy_button(gid, gid)
805
 
806
+ if st.session_state.get("username"):
807
  if st.button(f"Invite your friends to {gid}", key=f"invite_{gid}"):
808
+ friends = get_friends_map().get(st.session_state["username"], [])
809
  if not friends:
810
  st.warning("No friends to invite.")
811
  else:
812
  for f in friends:
813
+ send_game_invite(st.session_state["username"], f, gid)
814
  st.success("Invites sent to friends.")
815
 
816
  if st.button(f"Challenge friends with a new game like {gid}", key=f"challenge_{gid}"):
817
  new_id = create_game(
818
+ st.session_state.get("username", "Host"),
819
+ g.get("topics", []),
820
+ num_questions=len(g.get("questions", []))
821
  )
822
+ st.session_state["active_game_id"] = new_id
 
823
  st.session_state["game_questions"] = games.get(new_id, {}).get("questions", [])
824
  st.success(f"Challenge created: {new_id}")
825
+ st.rerun()
826
+
827
+
828
 
829
  # Weekly leaderboard
830
  st.subheader("๐Ÿ† Weekly Leaderboard (Top 10)")
 
1178
  def leaderboard_page():
1179
  st.header("Leaderboard")
1180
 
1181
+ # โœ… Correct source (Online + Offline safe)
1182
+ rows = unified_get("/leaderboard") or []
1183
+
1184
  if isinstance(rows, list) and rows:
1185
  df = pd.DataFrame(rows)
1186
  else:
 
1193
  st.info("No scores yet.")
1194
  return
1195
 
1196
+ # โœ… Ensure numeric score
1197
+ if "score" in df.columns:
1198
+ df["score"] = pd.to_numeric(df["score"], errors="coerce").fillna(0)
1199
+
1200
  # ๐Ÿ”’ Stable sorting
1201
  sort_cols = ["score"]
1202
  asc = [False]
1203
+
1204
  if "timestamp" in df.columns:
1205
  sort_cols.append("timestamp")
1206
  asc.append(True)
 
1214
  st.markdown(f"**๐Ÿ‘ฅ Total Players:** {total_players}")
1215
  st.markdown(f"**๐ŸŽฎ Total Games:** {total_games}")
1216
 
1217
+ # ๐Ÿ”ฅ TOP 3 PODIUM (overall)
1218
  st.markdown("### ๐Ÿ† Top 3 Overall")
1219
  top3 = df_sorted.head(3)
1220
+ for _, row in top3.iterrows():
1221
  st.write(
1222
  f"{row.get('avatar','๐ŸŽฎ')} **{row.get('name','Unknown')}** โ€” "
1223
  f"{row.get('score',0)} pts"
 
1231
  # Existing chart (unchanged)
1232
  fig = px.bar(
1233
  df_sorted.head(10),
1234
+ x="name",
1235
+ y="score"
1236
  )
1237
  st.plotly_chart(fig, use_container_width=True)
1238
 
1239
 
1240
+
1241
+ def get_weekly_leaderboard(limit=10):
1242
+ rows = unified_get("/leaderboard") or []
1243
  one_week_ago = datetime.utcnow() - timedelta(days=7)
1244
 
1245
  weekly = []
1246
  for r in rows:
1247
+ ts = r.get("timestamp")
1248
+ if not ts:
1249
+ continue
1250
+ try:
1251
+ played_time = datetime.fromisoformat(ts)
1252
+ except Exception:
1253
+ continue
1254
+
1255
+ if played_time >= one_week_ago:
1256
  weekly.append(r)
1257
 
1258
+ # Sort by score DESC
1259
  weekly.sort(key=lambda x: x.get("score", 0), reverse=True)
1260
+ return weekly[:limit]
1261
  # Route pages
1262
  if page == "Home":
1263
  home_page()