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) # ---------------------------------------------------- @app.route('/') def welcome_page(): logging.info("ROUTE: / [GET] - Rendering welcome page.") return render_template('welcome.html') @app.route('/begin_quest', methods=['POST']) 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')) @app.route('/main', methods=['GET', 'POST']) 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) @app.route('/summary') 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) @app.route('/restart_quest', methods=['POST']) 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)