Feature: 100-point scoring, question memory & spaced repetition, random wizard name generator, cache-busting v2.2, gold footer pills
This commit is contained in:
+67
-8
@@ -49,6 +49,7 @@ def format_question_for_client(q: Question, number: int, total: int, lang: str =
|
||||
random.shuffle(options_list)
|
||||
|
||||
return {
|
||||
"id": q.id,
|
||||
"question_number": number,
|
||||
"total_questions": total,
|
||||
"category": q.category,
|
||||
@@ -59,10 +60,15 @@ def format_question_for_client(q: Question, number: int, total: int, lang: str =
|
||||
}
|
||||
|
||||
def get_tier_evaluation(score: int, total: int = 10, lang: str = "de") -> dict:
|
||||
pct = (score / total) if total > 0 else 0
|
||||
if total <= 10 and score <= 10:
|
||||
pct = (score / total) if total > 0 else 0
|
||||
else:
|
||||
max_possible = (total * 100) if total <= 10 else total
|
||||
pct = (score / max_possible) if max_possible > 0 else 0
|
||||
|
||||
is_de = (lang != "en")
|
||||
|
||||
if score >= 10 or pct >= 1.0:
|
||||
if pct >= 0.90:
|
||||
grade = "O"
|
||||
title = "🧙♂️ Großmeister der Zauberei" if is_de else "🧙♂️ Grandmaster of Wizardry"
|
||||
subtitle = "Note: O (Ohnegleichen)" if is_de else "Grade: O (Outstanding)"
|
||||
@@ -76,7 +82,7 @@ def get_tier_evaluation(score: int, total: int = 10, lang: str = "de") -> dict:
|
||||
"The Ministry of Magic bows in deep awe!"
|
||||
)
|
||||
badge_class = "tier-outstanding"
|
||||
elif score >= 8 or pct >= 0.8:
|
||||
elif pct >= 0.70:
|
||||
grade = "E"
|
||||
title = "⚡ Meister-Hexe / Zauberer" if is_de else "⚡ Master Witch / Wizard"
|
||||
subtitle = "Note: E (Erwartungen übertroffen)" if is_de else "Grade: E (Exceeds Expectations)"
|
||||
@@ -88,7 +94,7 @@ def get_tier_evaluation(score: int, total: int = 10, lang: str = "de") -> dict:
|
||||
"Your wandwork is razor-sharp and your knowledge makes even Ravenclaws jealous."
|
||||
)
|
||||
badge_class = "tier-exceeds"
|
||||
elif score >= 5 or pct >= 0.5:
|
||||
elif pct >= 0.50:
|
||||
grade = "A"
|
||||
title = "📜 Solider Hogwarts-Schüler" if is_de else "📜 Solid Hogwarts Student"
|
||||
subtitle = "Note: A (Annehmbar)" if is_de else "Grade: A (Acceptable)"
|
||||
@@ -100,7 +106,7 @@ def get_tier_evaluation(score: int, total: int = 10, lang: str = "de") -> dict:
|
||||
"but you bravely fought your way through. A solid magical performance!"
|
||||
)
|
||||
badge_class = "tier-acceptable"
|
||||
elif score >= 2 or pct >= 0.2:
|
||||
elif pct >= 0.20:
|
||||
grade = "M"
|
||||
title = "🧹 Quaffel-Fänger in Not" if is_de else "🧹 Quaffle Catcher in Distress"
|
||||
subtitle = "Note: M (Mies)" if is_de else "Grade: P (Poor)"
|
||||
@@ -163,8 +169,43 @@ async def start_game(payload: GameStartRequest = None, db: Session = Depends(get
|
||||
if not pool:
|
||||
raise HTTPException(status_code=400, detail="Keine aktiven Fragen vorhanden.")
|
||||
|
||||
sample_size = min(10, len(pool))
|
||||
selected_questions = random.sample(pool, sample_size)
|
||||
recent_set = set(payload.recent_ids or []) if payload else set()
|
||||
wrong_set = set(payload.wrong_ids or []) if payload else set()
|
||||
|
||||
# Priority pool 1: Questions user previously failed (learning pool)
|
||||
wrong_pool = [q for q in pool if q.id in wrong_set]
|
||||
# Priority pool 2: Fresh questions not seen recently and not in wrong pool
|
||||
fresh_pool = [q for q in pool if q.id not in recent_set and q.id not in wrong_set]
|
||||
# Priority pool 3: Recent questions fallback
|
||||
recent_pool = [q for q in pool if q.id in recent_set and q.id not in wrong_set]
|
||||
|
||||
selected_questions = []
|
||||
# Up to 3 questions from wrong_pool for spaced repetition
|
||||
if wrong_pool:
|
||||
k_wrong = min(3, len(wrong_pool))
|
||||
selected_questions.extend(random.sample(wrong_pool, k_wrong))
|
||||
|
||||
# Fill remaining from fresh_pool
|
||||
needed = min(10, len(pool)) - len(selected_questions)
|
||||
if needed > 0 and fresh_pool:
|
||||
k_fresh = min(needed, len(fresh_pool))
|
||||
selected_questions.extend(random.sample(fresh_pool, k_fresh))
|
||||
|
||||
# If still needed, fill from recent_pool
|
||||
needed = min(10, len(pool)) - len(selected_questions)
|
||||
if needed > 0 and recent_pool:
|
||||
k_recent = min(needed, len(recent_pool))
|
||||
selected_questions.extend(random.sample(recent_pool, k_recent))
|
||||
|
||||
# Fallback to any remaining from pool
|
||||
needed = min(10, len(pool)) - len(selected_questions)
|
||||
if needed > 0:
|
||||
remaining = [q for q in pool if q not in selected_questions]
|
||||
if remaining:
|
||||
selected_questions.extend(random.sample(remaining, min(needed, len(remaining))))
|
||||
|
||||
# Shuffle to mix learning and fresh questions
|
||||
random.shuffle(selected_questions)
|
||||
question_ids = [q.id for q in selected_questions]
|
||||
session_id = str(uuid.uuid4())
|
||||
|
||||
@@ -211,6 +252,8 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
return {
|
||||
"correct": False,
|
||||
"score": session.score,
|
||||
"points_earned": 0,
|
||||
"question_id": None,
|
||||
"attempts_used": session.attempts_current,
|
||||
"attempts_left": 0,
|
||||
"is_finished": True,
|
||||
@@ -226,7 +269,15 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
correct = is_answer_correct(payload.answer, current_q.canonical_answer, current_q.accepted_answers, current_q.answer_en)
|
||||
|
||||
if correct:
|
||||
session.score += 1
|
||||
# Tiered points based on attempt
|
||||
if session.attempts_current == 0:
|
||||
points_earned = 100
|
||||
elif session.attempts_current == 1:
|
||||
points_earned = 70
|
||||
else:
|
||||
points_earned = 40
|
||||
|
||||
session.score += points_earned
|
||||
session.current_question_idx += 1
|
||||
session.attempts_current = 0
|
||||
|
||||
@@ -248,6 +299,8 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
return {
|
||||
"correct": True,
|
||||
"score": session.score,
|
||||
"points_earned": points_earned,
|
||||
"question_id": current_q_id,
|
||||
"attempts_used": 0,
|
||||
"attempts_left": 3,
|
||||
"is_finished": is_finished,
|
||||
@@ -286,6 +339,8 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
return {
|
||||
"correct": False,
|
||||
"score": session.score,
|
||||
"points_earned": 0,
|
||||
"question_id": current_q_id,
|
||||
"attempts_used": 3,
|
||||
"attempts_left": 0,
|
||||
"is_finished": is_finished,
|
||||
@@ -298,6 +353,8 @@ async def check_answer(payload: GameCheckRequest, db: Session = Depends(get_db))
|
||||
return {
|
||||
"correct": False,
|
||||
"score": session.score,
|
||||
"points_earned": 0,
|
||||
"question_id": current_q_id,
|
||||
"attempts_used": session.attempts_current,
|
||||
"attempts_left": attempts_left,
|
||||
"is_finished": False,
|
||||
@@ -330,6 +387,7 @@ async def get_hint(session_id: str, db: Session = Depends(get_db)):
|
||||
|
||||
@app.get("/finale", response_class=HTMLResponse)
|
||||
async def finale(request: Request, score: int = 0, total: int = 10, time: int = 0, lang: str = "de"):
|
||||
max_score = 1000 if (total <= 10 and score > 10) else (total * 100 if total <= 10 else total)
|
||||
tier = get_tier_evaluation(score, total, lang)
|
||||
if time >= 60:
|
||||
minutes = time // 60
|
||||
@@ -344,6 +402,7 @@ async def finale(request: Request, score: int = 0, total: int = 10, time: int =
|
||||
context={
|
||||
"score": score,
|
||||
"total": total,
|
||||
"max_score": max_score,
|
||||
"time_seconds": time,
|
||||
"time_formatted": time_str,
|
||||
"lang": lang,
|
||||
|
||||
Reference in New Issue
Block a user