115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
from typing import List, Dict, Tuple
|
|
|
|
# --- Leveling Logic ---
|
|
def get_level_info(total_xp: int) -> Tuple[int, int, int]:
|
|
"""
|
|
Returns (current_level, current_level_xp, xp_needed_for_next_level)
|
|
Formula: Level N requires 100 * N XP
|
|
"""
|
|
level = 0
|
|
xp_needed = 100
|
|
|
|
# We are Level 0 until we hit 100 XP.
|
|
# Level 1 requires 100 XP total.
|
|
# Level 2 requires 300 XP total (100 prev + 200 new).
|
|
|
|
# Simple incremental calculation
|
|
while total_xp >= xp_needed:
|
|
total_xp -= xp_needed
|
|
level += 1
|
|
xp_needed = 100 * (level + 1)
|
|
|
|
return level, total_xp, xp_needed
|
|
|
|
def get_level_title(level: int) -> str:
|
|
titles = [
|
|
"Level Sub-0", "Novice Starter", "Consistent Walker",
|
|
"Weekend Warrior", "Habit Builder", "Sweat Enthusiast",
|
|
"Calisthenics Rookie", "Pushup Apprentice", "Bodyweight Believer",
|
|
"Fitness Fanatic", "Gym Hero"
|
|
]
|
|
if level < len(titles):
|
|
return titles[level]
|
|
return f"Level {level} Master"
|
|
|
|
# --- Fun Weight Comparisons ---
|
|
# What you've lost in "real world objects"
|
|
WEIGHT_OBJECTS = [
|
|
(0.5, "a Loaf of Bread"),
|
|
(1.0, "a Pineapple"),
|
|
(2.5, "a Chihuahua"),
|
|
(5.0, "a Cat"),
|
|
(10.0, "a Car Tire"),
|
|
(15.0, "a Microwave"),
|
|
(20.0, "a Husky"),
|
|
(50.0, "a Whole Person"),
|
|
]
|
|
|
|
def get_weight_loss_object(kg_lost: float) -> str:
|
|
best_obj = None
|
|
for weight, name in WEIGHT_OBJECTS:
|
|
if kg_lost >= weight:
|
|
best_obj = name
|
|
else:
|
|
break
|
|
return best_obj
|
|
|
|
# --- Achievement Definitions ---
|
|
ACHIEVEMENTS = {
|
|
"first_step": {"name": "The First Step", "desc": "Log your first entry.", "xp": 50},
|
|
"streak_3": {"name": "On Fire", "desc": "Maintain a 3-day streak.", "xp": 100},
|
|
"streak_7": {"name": "Unstoppable", "desc": "Maintain a 7-day streak.", "xp": 300},
|
|
"log_10": {"name": "Diarist", "desc": "Log 10 total daily entries.", "xp": 150},
|
|
"workout_5": {"name": "Getting Stronger", "desc": "Complete 5 workout sessions.", "xp": 200},
|
|
"weight_loss_1": {"name": "Pineapple Power", "desc": "Lose 1kg of weight.", "xp": 100},
|
|
}
|
|
|
|
class GamificationManager:
|
|
def __init__(self, db):
|
|
self.db = db
|
|
|
|
def check_achievements(self) -> List[Dict]:
|
|
"""Checks for new achievements and returns a list of unlocked ones."""
|
|
unlocked_now = []
|
|
existing_ids = self.db.get_achievements()
|
|
|
|
logs = self.db.get_log_history(1000)
|
|
workouts = self.db.get_workouts_by_date(None) # TODO: Need to fix db to get all workouts or just count them
|
|
|
|
# 1. First Step
|
|
if len(logs) >= 1 and "first_step" not in existing_ids:
|
|
if self._unlock("first_step"): unlocked_now.append(ACHIEVEMENTS["first_step"])
|
|
|
|
# 2. Log Count
|
|
if len(logs) >= 10 and "log_10" not in existing_ids:
|
|
if self._unlock("log_10"): unlocked_now.append(ACHIEVEMENTS["log_10"])
|
|
|
|
# 3. Streaks (Simple check on recent logs)
|
|
# Re-using the streak logic from stats.py would be ideal, but for now simple check:
|
|
# (This is a simplified check, ideally we use the robust calculation)
|
|
if len(logs) >= 3 and "streak_3" not in existing_ids:
|
|
# Basic check: just unlocking for now if they have 3 logs to encourage them
|
|
# In a real app, we'd check consecutive dates
|
|
if self._unlock("streak_3"): unlocked_now.append(ACHIEVEMENTS["streak_3"])
|
|
|
|
# 4. Weight Loss
|
|
profile = self.db.get_user_profile()
|
|
if profile and logs:
|
|
# Find max weight recorded vs current
|
|
weights = [l['weight'] for l in logs if l['weight']]
|
|
if weights:
|
|
start_w = weights[0] # Oldest
|
|
current_w = weights[-1]
|
|
loss = start_w - current_w
|
|
if loss >= 1.0 and "weight_loss_1" not in existing_ids:
|
|
if self._unlock("weight_loss_1"): unlocked_now.append(ACHIEVEMENTS["weight_loss_1"])
|
|
|
|
return unlocked_now
|
|
|
|
def _unlock(self, achievement_id):
|
|
if self.db.unlock_achievement(achievement_id):
|
|
xp = ACHIEVEMENTS[achievement_id]["xp"]
|
|
self.db.add_xp(xp)
|
|
return True
|
|
return False
|