elkay frontend game xp
Browse files- dashboards/student_db.py +25 -28
- phase/Student_view/game.py +43 -47
dashboards/student_db.py
CHANGED
|
@@ -14,17 +14,6 @@ def load_css(file_name: str):
|
|
| 14 |
except FileNotFoundError:
|
| 15 |
st.warning("⚠️ Stylesheet not found. Please ensure 'assets/styles.css' exists.")
|
| 16 |
|
| 17 |
-
def _level_progress(total_xp: int, base: int = 500):
|
| 18 |
-
xp = int(max(0, total_xp))
|
| 19 |
-
level = max(1, xp // base + 1)
|
| 20 |
-
into = xp - (level - 1) * base
|
| 21 |
-
need = base
|
| 22 |
-
if into == need: # exact boundary
|
| 23 |
-
level += 1
|
| 24 |
-
into = 0
|
| 25 |
-
pct = int(round(100 * into / need)) if need else 0
|
| 26 |
-
return level, into, need, pct
|
| 27 |
-
|
| 28 |
def show_student_dashboard():
|
| 29 |
# Load CSS
|
| 30 |
css_path = os.path.join("assets", "styles.css")
|
|
@@ -150,29 +139,37 @@ def show_student_dashboard():
|
|
| 150 |
st.write("")
|
| 151 |
|
| 152 |
# --- XP Bar ---
|
| 153 |
-
#
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
st.markdown(
|
| 170 |
f"""
|
| 171 |
<div class="xp-card">
|
| 172 |
<span class="xp-level">Level {level}</span>
|
| 173 |
<span class="xp-text">{into:,} / {need:,} XP</span>
|
| 174 |
-
<div class="xp-bar">
|
| 175 |
-
|
|
|
|
|
|
|
| 176 |
</div>
|
| 177 |
""",
|
| 178 |
unsafe_allow_html=True
|
|
|
|
| 14 |
except FileNotFoundError:
|
| 15 |
st.warning("⚠️ Stylesheet not found. Please ensure 'assets/styles.css' exists.")
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
def show_student_dashboard():
|
| 18 |
# Load CSS
|
| 19 |
css_path = os.path.join("assets", "styles.css")
|
|
|
|
| 139 |
st.write("")
|
| 140 |
|
| 141 |
# --- XP Bar ---
|
| 142 |
+
# stats already fetched above
|
| 143 |
+
xp = int(stats.get("xp", 0))
|
| 144 |
+
level = int(stats.get("level", 1))
|
| 145 |
+
study_streak = int(stats.get("streak", 0))
|
| 146 |
+
|
| 147 |
+
# prefer server-provided per-level fields
|
| 148 |
+
into = int(stats.get("into", -1))
|
| 149 |
+
need = int(stats.get("need", -1))
|
| 150 |
+
|
| 151 |
+
# fallback if backend hasn't been updated to include into/need yet
|
| 152 |
+
if into < 0 or need <= 0:
|
| 153 |
+
base = 500
|
| 154 |
+
level = max(1, xp // base + 1)
|
| 155 |
+
start = (level - 1) * base
|
| 156 |
+
into = xp - start
|
| 157 |
+
need = base
|
| 158 |
+
if into == need:
|
| 159 |
+
level += 1
|
| 160 |
+
into = 0
|
| 161 |
+
|
| 162 |
+
pct = 0 if need <= 0 else min(100, int(round((into / need) * 100)))
|
| 163 |
|
| 164 |
st.markdown(
|
| 165 |
f"""
|
| 166 |
<div class="xp-card">
|
| 167 |
<span class="xp-level">Level {level}</span>
|
| 168 |
<span class="xp-text">{into:,} / {need:,} XP</span>
|
| 169 |
+
<div class="xp-bar">
|
| 170 |
+
<div class="xp-fill" style="width: {pct}%;"></div>
|
| 171 |
+
</div>
|
| 172 |
+
<div class="xp-total">Total XP: {xp:,}</div>
|
| 173 |
</div>
|
| 174 |
""",
|
| 175 |
unsafe_allow_html=True
|
phase/Student_view/game.py
CHANGED
|
@@ -16,16 +16,6 @@ def load_css(file_name: str):
|
|
| 16 |
|
| 17 |
st.session_state.setdefault("current_game", None)
|
| 18 |
|
| 19 |
-
def _level_progress(total_xp: int, base: int = 500):
|
| 20 |
-
xp = int(max(0, total_xp))
|
| 21 |
-
level = max(1, xp // base + 1)
|
| 22 |
-
into = xp - (level - 1) * base
|
| 23 |
-
need = base
|
| 24 |
-
if into == need:
|
| 25 |
-
level += 1
|
| 26 |
-
into = 0
|
| 27 |
-
pct = int(round(100 * into / need)) if need else 0
|
| 28 |
-
return level, into, need, pct
|
| 29 |
|
| 30 |
# --- GAME RENDERERS ---
|
| 31 |
def _render_budget_builder():
|
|
@@ -144,40 +134,36 @@ def _load_leaderboard(user_id: int, limit: int = 10) -> list[dict]:
|
|
| 144 |
class_id = st.session_state.get("current_class_id")
|
| 145 |
|
| 146 |
# try to pick a class automatically if none set (optional)
|
| 147 |
-
if not class_id:
|
| 148 |
try:
|
| 149 |
-
|
| 150 |
-
if USE_LOCAL_DB and hasattr(dbapi, "list_classes_for_student"):
|
| 151 |
-
classes = dbapi.list_classes_for_student(user_id) or []
|
| 152 |
-
else:
|
| 153 |
-
classes = api.list_classes_for_student(user_id) or []
|
| 154 |
if classes:
|
| 155 |
class_id = classes[0]["class_id"]
|
| 156 |
st.session_state.current_class_id = class_id
|
| 157 |
except Exception:
|
| 158 |
pass
|
| 159 |
|
| 160 |
-
#
|
| 161 |
-
rows = []
|
| 162 |
try:
|
| 163 |
-
if
|
| 164 |
rows = dbapi.leaderboard_for_class(class_id, limit=limit) or []
|
| 165 |
-
elif
|
| 166 |
rows = dbapi.leaderboard_global(limit=limit) or []
|
| 167 |
else:
|
| 168 |
-
#
|
| 169 |
-
|
| 170 |
-
if class_id:
|
| 171 |
-
|
| 172 |
-
|
| 173 |
rows.append({
|
| 174 |
"user_id": m.get("student_id"),
|
| 175 |
"name": m.get("name") or m.get("email") or "Student",
|
| 176 |
-
"xp":
|
| 177 |
-
"level":
|
| 178 |
})
|
| 179 |
except Exception as e:
|
| 180 |
st.warning(f"Leaderboard error: {e}")
|
|
|
|
| 181 |
|
| 182 |
# ensure current user present
|
| 183 |
if not any(r.get("user_id") == user_id for r in rows):
|
|
@@ -269,36 +255,45 @@ def show_games():
|
|
| 269 |
# pull live XP/level
|
| 270 |
user_id = st.session_state.user["user_id"]
|
| 271 |
|
| 272 |
-
# DB if enabled
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
stats = api.user_stats(user_id)
|
| 278 |
-
|
| 279 |
-
|
|
|
|
| 280 |
|
| 281 |
total_xp = int(stats.get("xp", 0))
|
| 282 |
-
|
| 283 |
-
st.session_state.xp
|
| 284 |
st.session_state.streak = int(stats.get("streak", 0))
|
| 285 |
|
|
|
|
| 286 |
into = stats.get("into")
|
| 287 |
need = stats.get("need")
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
st.markdown(f"""
|
| 299 |
<div style="background:#e0e0e0;border-radius:12px;padding:3px;width:100%;">
|
| 300 |
<div style="
|
| 301 |
-
width:{
|
| 302 |
background:linear-gradient(135deg,#22c55e,#059669);
|
| 303 |
height:24px;border-radius:10px;text-align:right;
|
| 304 |
color:white;font-weight:bold;padding-right:8px;line-height:24px;">
|
|
@@ -310,6 +305,7 @@ def show_games():
|
|
| 310 |
|
| 311 |
|
| 312 |
|
|
|
|
| 313 |
st.markdown("---")
|
| 314 |
|
| 315 |
# Game list
|
|
|
|
| 16 |
|
| 17 |
st.session_state.setdefault("current_game", None)
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# --- GAME RENDERERS ---
|
| 21 |
def _render_budget_builder():
|
|
|
|
| 134 |
class_id = st.session_state.get("current_class_id")
|
| 135 |
|
| 136 |
# try to pick a class automatically if none set (optional)
|
| 137 |
+
if not class_id and hasattr(dbapi, "list_classes_for_student"):
|
| 138 |
try:
|
| 139 |
+
classes = dbapi.list_classes_for_student(user_id) or []
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
if classes:
|
| 141 |
class_id = classes[0]["class_id"]
|
| 142 |
st.session_state.current_class_id = class_id
|
| 143 |
except Exception:
|
| 144 |
pass
|
| 145 |
|
| 146 |
+
# fetch rows from DB
|
|
|
|
| 147 |
try:
|
| 148 |
+
if class_id and hasattr(dbapi, "leaderboard_for_class"):
|
| 149 |
rows = dbapi.leaderboard_for_class(class_id, limit=limit) or []
|
| 150 |
+
elif hasattr(dbapi, "leaderboard_global"):
|
| 151 |
rows = dbapi.leaderboard_global(limit=limit) or []
|
| 152 |
else:
|
| 153 |
+
# fallback using existing function if needed
|
| 154 |
+
rows = []
|
| 155 |
+
if class_id and hasattr(dbapi, "class_student_metrics"):
|
| 156 |
+
for m in dbapi.class_student_metrics(class_id) or []:
|
| 157 |
+
xp = int(m.get("total_xp", 0))
|
| 158 |
rows.append({
|
| 159 |
"user_id": m.get("student_id"),
|
| 160 |
"name": m.get("name") or m.get("email") or "Student",
|
| 161 |
+
"xp": xp,
|
| 162 |
+
"level": dbapi.level_from_xp(xp),
|
| 163 |
})
|
| 164 |
except Exception as e:
|
| 165 |
st.warning(f"Leaderboard error: {e}")
|
| 166 |
+
rows = []
|
| 167 |
|
| 168 |
# ensure current user present
|
| 169 |
if not any(r.get("user_id") == user_id for r in rows):
|
|
|
|
| 255 |
# pull live XP/level
|
| 256 |
user_id = st.session_state.user["user_id"]
|
| 257 |
|
| 258 |
+
# Prefer local DB only if enabled. Otherwise call backend.
|
| 259 |
+
if USE_LOCAL_DB and hasattr(dbapi, "user_xp_and_level"):
|
| 260 |
+
stats = dbapi.user_xp_and_level(user_id) # {'xp', 'level', 'streak', maybe 'into','need'}
|
| 261 |
+
else:
|
| 262 |
+
try:
|
| 263 |
+
stats = api.user_stats(user_id) # backend /students/{id}/stats
|
| 264 |
+
except Exception as e:
|
| 265 |
+
# hard fallback so the page still renders
|
| 266 |
+
stats = {"xp": int(st.session_state.get("xp", 0)), "level": 1, "streak": 0}
|
| 267 |
|
| 268 |
total_xp = int(stats.get("xp", 0))
|
| 269 |
+
level = int(stats.get("level", 1))
|
| 270 |
+
st.session_state.xp = total_xp
|
| 271 |
st.session_state.streak = int(stats.get("streak", 0))
|
| 272 |
|
| 273 |
+
# Use server-provided per-level values when available
|
| 274 |
into = stats.get("into")
|
| 275 |
need = stats.get("need")
|
| 276 |
|
| 277 |
+
# Fallback if backend has not been updated yet
|
| 278 |
+
if into is None or need in (None, 0):
|
| 279 |
+
base = 500
|
| 280 |
+
level = max(1, total_xp // base + 1)
|
| 281 |
+
start = (level - 1) * base
|
| 282 |
+
into = total_xp - start
|
| 283 |
+
need = base
|
| 284 |
+
if into == need:
|
| 285 |
+
level += 1
|
| 286 |
+
into = 0
|
| 287 |
+
|
| 288 |
+
into = int(max(0, into))
|
| 289 |
+
need = int(max(1, need))
|
| 290 |
+
progress_pct = min(100, int(round((into / need) * 100)))
|
| 291 |
+
|
| 292 |
+
st.write(f"Level {level} Experience Points")
|
| 293 |
st.markdown(f"""
|
| 294 |
<div style="background:#e0e0e0;border-radius:12px;padding:3px;width:100%;">
|
| 295 |
<div style="
|
| 296 |
+
width:{progress_pct}%;
|
| 297 |
background:linear-gradient(135deg,#22c55e,#059669);
|
| 298 |
height:24px;border-radius:10px;text-align:right;
|
| 299 |
color:white;font-weight:bold;padding-right:8px;line-height:24px;">
|
|
|
|
| 305 |
|
| 306 |
|
| 307 |
|
| 308 |
+
|
| 309 |
st.markdown("---")
|
| 310 |
|
| 311 |
# Game list
|