Spaces:
Sleeping
Sleeping
| from flask import Flask, render_template, session, redirect, url_for, request | |
| import time | |
| import os | |
| import logging | |
| from werkzeug.middleware.proxy_fix import ProxyFix | |
| # ---------------------------------------------------- | |
| # 1. Logging Setup (Your code is perfect) | |
| # ---------------------------------------------------- | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s' | |
| ) | |
| # ---------------------------------------------------- | |
| # 2. Flask App Initialization (WITH THE FIXES) | |
| # ---------------------------------------------------- | |
| app = Flask(__name__) | |
| # A. SECRET_KEY setup (Your logic is fine, just made the error message more explicit) | |
| app.secret_key = os.environ.get('SECRET_KEY') | |
| if not app.secret_key: | |
| logging.error("FATAL: SECRET_KEY environment variable is not set. Sessions will fail.") | |
| app.secret_key = 'fallback-key-for-dev-only-not-secure' | |
| logging.warning("⚠️ Using insecure fallback secret key. Set SECRET_KEY in production.") | |
| # B. ProxyFix (Enhanced for robustness on Hugging Face) | |
| app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2, x_proto=1, x_host=1, x_prefix=1) | |
| # C. Secure cookie must be true | |
| app.config['SESSION_COOKIE_SECURE'] = True | |
| # D. *** THE CRITICAL ONE-SHOT FIX *** | |
| # Changed from 'Lax' to 'None' to allow the session to work inside the Hugging Face iframe. | |
| app.config['SESSION_COOKIE_SAMESITE'] = 'None' | |
| logging.info("✅ Flask App Initialized with FINAL Production Settings for iframe environments.") | |
| # ---------------------------------------------------- | |
| # 3. Game Content (Your code is perfect) | |
| # ---------------------------------------------------- | |
| videos = [ | |
| "static/videos/Animated_Drought_Relief_Video_Creation.mp4", | |
| "static/videos/Animated_Rainwater_Harvesting_Video.mp4", | |
| "static/videos/Animated_Water_Filtration_Process_Video.mp4", | |
| "static/videos/Water_Filtration_Animation_Ready.mp4", | |
| "static/videos/Animated_Rainwater_Usage_Video.mp4", | |
| "static/videos/Water_Hero_Video_Generation_Complete.mp4" | |
| ] | |
| video_descriptions = [ | |
| "🌍 Water Crisis Alert", "🏠 Collection Master", "💧 Purification Expert", | |
| "🗃️ Storage Specialist", "♻️ Usage Strategist", "🌱 Environmental Champion" | |
| ] | |
| task_scenarios = [ | |
| {"question": "🌍 After watching the video, what is the FIRST step to address water scarcity at your home?", | |
| "choices": [ | |
| {"action": "🌧️ Start collecting rainwater immediately", "description": "Begin harvesting nature's free water supply", "consequence": "✅ SMART START! You're taking the first step toward water independence! +100 XP", "xp_reward": 100, "icon": "🏆"}, | |
| {"action": "🚿 Take longer showers to enjoy water while it lasts", "description": "Use more water before it runs out", "consequence": "❌ WRONG APPROACH! This worsens the problem. Conservation is key! -20 Health", "xp_reward": 0, "icon": "💸"}, | |
| {"action": "⏳ Wait for government to solve the problem", "description": "Rely on authorities to fix water issues", "consequence": "❌ MISSED OPPORTUNITY! Individual action is crucial. Start now! -20 Health", "xp_reward": 0, "icon": "⏰"} | |
| ]}, | |
| {"question": "🌧️ It's raining heavily! Your house has a metal roof. What's your FIRST action as a Water Conservation Hero?", | |
| "choices": [ | |
| {"action": "🏠 Install gutters and downspouts on your roof", "description": "Channel rainwater efficiently into collection system", "consequence": "✅ SMART MOVE! You capture 80% of the rainfall. +100 XP", "xp_reward": 100, "icon": "🏆"}, | |
| {"action": "🛣️ Let the water flow down the streets", "description": "Allow natural drainage", "consequence": "❌ MISSED OPPORTUNITY! Thousands of liters wasted. -20 Health", "xp_reward": 0, "icon": "💸"}, | |
| {"action": "🛍️ Place plastic bags under the roof edge", "description": "Use bags to catch water", "consequence": "❌ INEFFICIENT! Bags tear easily. -20 Health", "xp_reward": 0, "icon": "🗑️"} | |
| ]}, | |
| {"question": "💧 Your collection tank is filling up with rainwater, but you notice leaves and debris. What's your next step?", | |
| "choices": [ | |
| {"action": "🔍 Install a first-flush diverter and filtration system", "description": "Remove initial dirty water", "consequence": "✅ EXCELLENT CHOICE! Clean water ready. +100 XP", "xp_reward": 100, "icon": "🧪"}, | |
| {"action": "⚠️ Use dirty water directly", "description": "Proceed with unfiltered water", "consequence": "❌ HEALTH HAZARD! Contamination risk. -20 Health", "xp_reward": 0, "icon": "☠️"}, | |
| {"action": "🧪 Add household bleach", "description": "Chemical treatment", "consequence": "❌ WRONG CHEMICAL! Unsafe for water. -20 Health", "xp_reward": 0, "icon": "⚗️"} | |
| ]}, | |
| {"question": "🏺 You have 500 liters of clean rainwater. Where should you store it?", | |
| "choices": [ | |
| {"action": "🏺 Underground concrete tank with cover", "description": "Safe long-term storage", "consequence": "✅ MASTER STRATEGY! Stored safely. +100 XP", "xp_reward": 100, "icon": "🏛️"}, | |
| {"action": "🪣 Store in open buckets", "description": "Easy access containers", "consequence": "❌ BREEDING GROUND! Mosquito risk. -20 Health", "xp_reward": 0, "icon": "🦟"}, | |
| {"action": "🌍 Dig a pit and let water sit", "description": "Natural storage", "consequence": "❌ CONTAMINATION RISK! Polluted. -20 Health", "xp_reward": 0, "icon": "️"} | |
| ]}, | |
| {"question": "♻️ Dry season! You have 200L left. Neighbor needs help. What do you do?", | |
| "choices": [ | |
| {"action": "🌿 Use for drip irrigation and toilet flushing", "description": "Strategic usage", "consequence": "✅ WATER WISDOM! Saved 300L. +100 XP", "xp_reward": 100, "icon": "🌱"}, | |
| {"action": "⏰ Save all water", "description": "Conservative approach", "consequence": "❌ MISSED IMPACT! Water can spoil. -20 Health", "xp_reward": 0, "icon": "⏰"}, | |
| {"action": "🚽 Mix with sewage", "description": "Combine with waste", "consequence": "❌ TERRIBLE WASTE! Never mix. -20 Health", "xp_reward": 0, "icon": "🤮"} | |
| ]}, | |
| {"question": "🌱 How will you inspire your community?", | |
| "choices": [ | |
| {"action": "👥 Organize workshops", "description": "Teach others", "consequence": "✅ COMMUNITY HERO! Inspired 50 families. +100 XP", "xp_reward": 100, "icon": "🌟"}, | |
| {"action": "🤐 Keep it to yourself", "description": "Exclusive access", "consequence": "❌ SELFISH! Collective action needed. -20 Health", "xp_reward": 0, "icon": "🚫"}, | |
| {"action": "📱 Post online occasionally", "description": "Minimal effort", "consequence": "❌ MINIMAL IMPACT! Real change needs more. -20 Health", "xp_reward": 0, "icon": "📱"} | |
| ]} | |
| ] | |
| achievements_list = [ | |
| {"name": "🌟 First Steps", "desc": "Started your water journey", "condition": "start"}, | |
| {"name": "💧 Drop Collector", "desc": "Learned about collection", "condition": "video_1"}, | |
| {"name": "🔬 Purification Pro", "desc": "Mastered filtration", "condition": "video_2"}, | |
| {"name": "🏺 Storage Sage", "desc": "Understood storage methods", "condition": "video_3"}, | |
| {"name": "♻️ Usage Expert", "desc": "Optimized water usage", "condition": "video_4"}, | |
| {"name": "🎯 Perfect Score", "desc": "100% accuracy achieved", "condition": "perfect"}, | |
| {"name": "🔥 On Fire", "desc": "3 correct answers in a row", "condition": "streak_3"}, | |
| {"name": "🌍 Water Hero", "desc": "Completed the full quest", "condition": "complete"} | |
| ] | |
| # ---------------------------------------------------- | |
| # 4. Game Logic (Your code is perfect) | |
| # ---------------------------------------------------- | |
| def calculate_level(): | |
| return min(10, 1 + session.get('xp_points', 0) // 100) | |
| def add_xp(amount: int): | |
| if 'xp_points' not in session: | |
| resume_or_initialize() | |
| old_level = session.get('player_level', 1) | |
| session['xp_points'] = session.get('xp_points', 0) + amount | |
| session['player_level'] = calculate_level() | |
| if session['player_level'] > old_level: | |
| session['message'] = f"🎉 LEVEL UP! You reached Level {session['player_level']}!" | |
| def unlock_achievement(condition: str): | |
| if 'achievements' not in session: | |
| resume_or_initialize() | |
| for a in achievements_list: | |
| if a["condition"] == condition and a not in session['achievements']: | |
| # Use append to modify the list in place | |
| session['achievements'].append(a) | |
| session.modified = True # Mark session as modified | |
| session['message'] = f"🏆 Achievement Unlocked: {a['name']} - {a['desc']}" | |
| add_xp(50) | |
| def reset_game_state(): | |
| session.clear() # Use clear for a full reset | |
| session["video_index"] = 0 | |
| session["feedback"] = "" | |
| session["quiz_correct_count"] = 0 | |
| session["quiz_attempts"] = 0 | |
| session["player_level"] = 1 | |
| session["xp_points"] = 0 | |
| session["achievements"] = [] | |
| session["streak"] = 0 | |
| session["health"] = 100 | |
| session["video_watched"] = False | |
| session["question_answered"] = False | |
| session["message"] = "" | |
| def resume_or_initialize(): | |
| if "player_level" not in session: | |
| logging.info("🔁 No active session found. Initializing new game state...") | |
| reset_game_state() | |
| def next_video_logic(): | |
| if session['video_index'] < len(videos) - 1: | |
| session['video_index'] += 1 | |
| session['feedback'] = "" | |
| session['video_watched'] = False | |
| session['question_answered'] = False | |
| session['message'] = "" | |
| if session['video_index'] > 0: | |
| unlock_achievement(f"video_{session['video_index']}") | |
| return redirect(url_for('main_app')) | |
| else: | |
| unlock_achievement("complete") | |
| return redirect(url_for('summary_page')) | |
| def select_card_logic(choice_index: int): | |
| current_scenario = task_scenarios[session['video_index']] | |
| choice = current_scenario["choices"][choice_index] | |
| is_correct = choice["xp_reward"] > 0 | |
| session['quiz_attempts'] += 1 | |
| session['question_answered'] = True | |
| session['feedback'] = choice["consequence"] | |
| session['message'] = "" | |
| if is_correct: | |
| session['quiz_correct_count'] += 1 | |
| session['streak'] += 1 | |
| add_xp(choice["xp_reward"]) | |
| if session['streak'] >= 3: | |
| unlock_achievement("streak_3") | |
| else: | |
| session['streak'] = 0 | |
| session['health'] = max(0, session['health'] - 20) | |
| # ---------------------------------------------------- | |
| # 5. Flask Routes (Your code is perfect) | |
| # ---------------------------------------------------- | |
| def welcome_page(): | |
| logging.info("ROUTE: / [GET] - Rendering welcome page.") | |
| return render_template('welcome.html') | |
| def begin_quest(): | |
| logging.info("ROUTE: /begin_quest [POST] - Quest beginning.") | |
| reset_game_state() | |
| unlock_achievement("start") | |
| logging.info(f"✅ Session after init: {dict(session)}") | |
| return redirect(url_for('main_app')) | |
| def main_app(): | |
| resume_or_initialize() | |
| if request.method == 'POST': | |
| logging.info("ROUTE: /main [POST] - Received POST request. Session is present.") | |
| if 'mark_watched' in request.form: | |
| logging.info("Video marked as watched.") | |
| session['video_watched'] = True | |
| session['message'] = "" | |
| elif 'select_choice' in request.form: | |
| select_card_logic(int(request.form['select_choice'])) | |
| elif 'next_level' in request.form or 'continue_quest' in request.form: | |
| return next_video_logic() | |
| elif 'replay_video' in request.form: | |
| session['feedback'] = "" | |
| session['video_watched'] = False | |
| session['question_answered'] = False | |
| session['message'] = "🔄 Video ready for replay!" | |
| elif 'main_menu' in request.form: | |
| return redirect(url_for('welcome_page')) | |
| return redirect(url_for('main_app')) | |
| idx = session['video_index'] | |
| context = { | |
| 'current_video_index': idx, 'total_videos': len(videos), | |
| 'video_src': url_for('static', filename=videos[idx].replace('static/', '', 1)), | |
| 'video_description': video_descriptions[idx], 'current_scenario': task_scenarios[idx], | |
| 'player_level': session['player_level'], 'xp_points': session['xp_points'], | |
| 'health': session['health'], 'streak': session['streak'], | |
| 'quiz_correct_count': session['quiz_correct_count'], 'quiz_attempts': session['quiz_attempts'], | |
| 'video_watched': session['video_watched'], 'question_answered': session['question_answered'], | |
| 'feedback': session['feedback'], 'achievements': session['achievements'], | |
| 'message': session.pop('message', '') | |
| } | |
| return render_template('main.html', **context) | |
| def summary_page(): | |
| resume_or_initialize() | |
| total_quizzes = len(task_scenarios) | |
| score = session.get('quiz_correct_count', 0) | |
| if score == total_quizzes and total_quizzes > 0: | |
| unlock_achievement("perfect") | |
| context = { | |
| 'player_level': session['player_level'], 'xp_points': session['xp_points'], | |
| 'quiz_score': score, 'total_quizzes': total_quizzes, | |
| 'accuracy': (score / max(1, session['quiz_attempts'])) * 100, | |
| 'achievements': session['achievements'], 'current_date': time.strftime("%B %d, %Y"), | |
| 'message': session.pop('message', '') | |
| } | |
| return render_template('summary.html', **context) | |
| def restart_quest(): | |
| reset_game_state() | |
| return redirect(url_for('welcome_page')) | |
| # ---------------------------------------------------- | |
| # 6. Run App (Your code is perfect) | |
| # ---------------------------------------------------- | |
| if __name__ == '__main__': | |
| app.run(debug=True, port=5000) |