Reworked 'Levels' for progress

This commit is contained in:
2026-01-10 17:05:43 +01:00
parent 2c99454951
commit febb7ae87b
7 changed files with 241 additions and 14 deletions

114
gamification.py Normal file
View File

@@ -0,0 +1,114 @@
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