Personalized_Learning_App / gamification.py
Kiki0203's picture
Upload 13 files
f9766bc verified
"""
gamification.py
Handles XP, streaks, badges, and levels for LearnCraft.
"""
import json
import os
from datetime import date, timedelta
GAMIFICATION_FILE = "gamification.json"
# ── XP values ────────────────────────────────────────────────────────────────
XP_STUDY_SESSION = 10
XP_QUIZ_COMPLETE = 20
XP_PERFECT_SCORE = 50
XP_SCORE_ABOVE_80 = 30
XP_SCORE_ABOVE_60 = 15
XP_FLASHCARD_DECK = 10
XP_STREAK_BONUS = 5 # per day of streak
# ── Level thresholds ─────────────────────────────────────────────────────────
LEVELS = [
(0, "🌱 Seedling"),
(50, "πŸ“– Reader"),
(150, "πŸŽ“ Student"),
(300, "πŸ”¬ Scholar"),
(500, "πŸ† Expert"),
(800, "🌟 Master"),
(1200, "πŸš€ Genius"),
]
# ── Badge definitions ─────────────────────────────────────────────────────────
BADGES = {
"first_quiz": {"name": "First Quiz", "icon": "🎯", "desc": "Complete your first quiz"},
"perfect_score": {"name": "Perfect Score", "icon": "πŸ’―", "desc": "Score 100% on a quiz"},
"streak_3": {"name": "3-Day Streak", "icon": "πŸ”₯", "desc": "Study 3 days in a row"},
"streak_7": {"name": "Week Warrior", "icon": "⚑", "desc": "Study 7 days in a row"},
"streak_14": {"name": "Fortnight Hero", "icon": "πŸ—“οΈ", "desc": "Study 14 days in a row"},
"topics_5": {"name": "Explorer", "icon": "πŸ—ΊοΈ", "desc": "Study 5 different topics"},
"topics_10": {"name": "Polymath", "icon": "🧠", "desc": "Study 10 different topics"},
"quizzes_10": {"name": "Quiz Master", "icon": "🧩", "desc": "Complete 10 quizzes"},
"quizzes_25": {"name": "Quiz Champion", "icon": "πŸ…", "desc": "Complete 25 quizzes"},
"score_above_80": {"name": "High Achiever", "icon": "πŸŽ‰", "desc": "Score above 80% on a quiz"},
"flashcards": {"name": "Card Shark", "icon": "πŸƒ", "desc": "Complete a flashcard deck"},
"level_scholar": {"name": "Scholar", "icon": "πŸ”¬", "desc": "Reach Scholar level (300 XP)"},
"level_expert": {"name": "Expert", "icon": "πŸ†", "desc": "Reach Expert level (500 XP)"},
"level_master": {"name": "Master", "icon": "🌟", "desc": "Reach Master level (800 XP)"},
"notes_saver": {"name": "Note Taker", "icon": "πŸ“", "desc": "Save your first note"},
}
def load_gamification() -> dict:
if os.path.exists(GAMIFICATION_FILE):
try:
with open(GAMIFICATION_FILE, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
pass
return {
"xp": 0,
"badges": [],
"streak": 0,
"last_study_date": None,
"total_quizzes": 0,
"study_dates": [],
}
def save_gamification(data: dict) -> None:
try:
with open(GAMIFICATION_FILE, "w") as f:
json.dump(data, f, indent=2)
except IOError:
pass
def get_level(xp: int) -> tuple:
"""Return (level_name, xp_for_next, xp_in_current_level, progress_pct)."""
current_level = LEVELS[0]
next_level = None
for i, (threshold, name) in enumerate(LEVELS):
if xp >= threshold:
current_level = (threshold, name)
next_level = LEVELS[i + 1] if i + 1 < len(LEVELS) else None
if next_level:
xp_start = current_level[0]
xp_end = next_level[0]
progress = (xp - xp_start) / (xp_end - xp_start)
return current_level[1], next_level[1], xp_end - xp, round(progress * 100)
return current_level[1], None, 0, 100
def update_streak(data: dict) -> dict:
today = str(date.today())
yesterday = str(date.today() - timedelta(days=1))
last = data.get("last_study_date")
study_dates = data.get("study_dates", [])
if today not in study_dates:
study_dates.append(today)
data["study_dates"] = study_dates
if last == today:
pass # already counted today
elif last == yesterday:
data["streak"] = data.get("streak", 0) + 1
data["last_study_date"] = today
else:
data["streak"] = 1
data["last_study_date"] = today
return data
def award_xp(data: dict, amount: int, reason: str = "") -> tuple:
"""Add XP and return (new_data, xp_awarded, level_up_msg)."""
old_xp = data.get("xp", 0)
old_level = get_level(old_xp)[0]
data["xp"] = old_xp + amount
new_level = get_level(data["xp"])[0]
level_up = new_level if new_level != old_level else None
return data, amount, level_up
def check_and_award_badges(data: dict, context: dict) -> list:
"""
Check badge conditions and award new ones.
context keys: score, topics_count, quizzes_count, event
Returns list of newly awarded badge keys.
"""
earned = set(data.get("badges", []))
new_ones = []
score = context.get("score", -1)
topics_count = context.get("topics_count", 0)
quizzes_count = context.get("quizzes_count", 0)
event = context.get("event", "")
streak = data.get("streak", 0)
xp = data.get("xp", 0)
checks = {
"first_quiz": quizzes_count >= 1,
"perfect_score": score == 100,
"streak_3": streak >= 3,
"streak_7": streak >= 7,
"streak_14": streak >= 14,
"topics_5": topics_count >= 5,
"topics_10": topics_count >= 10,
"quizzes_10": quizzes_count >= 10,
"quizzes_25": quizzes_count >= 25,
"score_above_80": score >= 80,
"flashcards": event == "flashcards",
"level_scholar": xp >= 300,
"level_expert": xp >= 500,
"level_master": xp >= 800,
"notes_saver": event == "note_saved",
}
for key, condition in checks.items():
if condition and key not in earned:
earned.add(key)
new_ones.append(key)
data["badges"] = list(earned)
return new_ones
def record_quiz(data: dict, score: int, topics_count: int) -> dict:
data["total_quizzes"] = data.get("total_quizzes", 0) + 1
return data
def get_xp_for_quiz(score: int) -> int:
xp = XP_QUIZ_COMPLETE
if score == 100:
xp += XP_PERFECT_SCORE
elif score >= 80:
xp += XP_SCORE_ABOVE_80
elif score >= 60:
xp += XP_SCORE_ABOVE_60
return xp