File size: 28,865 Bytes
05dff9f
 
 
 
 
 
 
 
 
 
 
 
a88a491
05dff9f
 
a88a491
05dff9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a88a491
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
a88a491
 
 
05dff9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a88a491
 
 
05dff9f
 
 
 
 
 
a88a491
 
05dff9f
a88a491
 
 
 
 
 
 
05dff9f
a88a491
 
05dff9f
a88a491
 
 
05dff9f
 
a88a491
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05dff9f
 
 
 
 
 
 
a88a491
 
 
 
 
 
 
 
05dff9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
import math
from flask import Flask, render_template, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = 'supersecretkey_for_session_management' # Required for flash messages and session

# --- In-Memory Content Definitions ---

# Videos (ID, Title, YouTube URL, Length, Base Points for Completion)
# Renamed to RAW as we'll augment it with position and path data
VIDEOS_RAW = [
    {"id": "v1", "title": "A Beginner's Guide to Rainwater Collection", "url": "https://www.youtube.com/embed/uPS3GT1DC_Q?si=OYkUG-tuJeZquIkv", "length": "10:51", "base_points": 50},
    {"id": "v2", "title": "Introduction to Rainwater Harvesting", "url": "https://www.youtube.com/embed/72CVryFbv5I?si=oKWZ5Ep1Z3kcLI-g", "length": "6:44", "base_points": 40},
    {"id": "v3", "title": "First Flush Diverter Explained", "url": "https://www.youtube.com/embed/zO09yau1lCo?si=g-EJqsLU3PNiWjRD", "length": "8:16", "base_points": 60},
    {"id": "v4", "title": "Is Rainwater Safe to Drink?", "url": "https://www.youtube.com/embed/nLphSEiHroU?si=PJ49EHuKVxeYRt_H", "length": "7:00", "base_points": 70}, # Adjusted length for the new video
    {"id": "v5", "title": "First Flush Installation", "url": "https://www.youtube.com/embed/bbYJvTj8uiw?si=nClbPzN9L2buoilz", "length": "9:34", "base_points": 65},
    {"id": "v6", "title": "DIY Home System Tour", "url": "https://www.youtube.com/embed/J2t_ZZGmBoo?si=LpSyztIH8xtF_Z_3", "length": "10:27", "base_points": 80},
    {"id": "v7", "title": "Upgrading a Large System", "url": "https://www.youtube.com/embed/-NJ8aS4_WDA?si=2C0vCgr2O2hiMJx6", "length": "9:01", "base_points": 75},
    {"id": "v8", "title": "Living on Rainwater in a Desert Environment", "url": "https://www.youtube.com/embed/rxmspcSjjKM?si=TerHbg_Ny_ZjbEy3", "length": "10:43", "base_points": 90},
    {"id": "v9", "title": "Polyethylene Tank Repair", "url": "https://www.youtube.com/embed/R1CCQXHUvNE?si=XEn2Jwdza5u5x_2o", "length": "11:00", "base_points": 55},
    {"id": "v10", "title": "Pump System Overview", "url": "https://www.youtube.com/embed/-yhWEkXqVR0?si=eWeutKwzNdpJGIOy", "length": "7:30", "base_points": 85},
]
# NOTE: The YouTube URLs have been updated with the actual video embed URLs.

# Fixed node positions (percentage from left/top of map-area)
# These are floats for easier calculation in Python.
NODE_POSITIONS = {
    'v1': {'top': 90, 'left': 20},
    'v2': {'top': 80, 'left': 35},
    'v3': {'top': 70, 'left': 20},
    'v4': {'top': 60, 'left': 45},
    'v5': {'top': 50, 'left': 25},
    'v6': {'top': 40, 'left': 50},
    'v7': {'top': 30, 'left': 30},
    'v8': {'top': 20, 'left': 60},
    'v9': {'top': 10, 'left': 40},
    'v10': {'top': 5, 'left': 75}
}

# --- Augment VIDEOS_RAW with positions and path data ---
VIDEOS = []
prev_node_pos_data = None # Stores {'top': float, 'left': float} for the previous node

# Assuming a hypothetical map_area pixel dimension for path calculations
# These are used to convert % to px for path length calculation.
# In a real responsive app, for truly accurate paths, these dimensions might need to be dynamic (e.e.g, from JS on client).
# For now, these fixed values provide a consistent visual ratio.
MAP_AREA_WIDTH_PX = 800 # Represents 100% width of the map-area
MAP_AREA_HEIGHT_PX = 700 # Represents 100% height of the map-area

for i, video_data in enumerate(VIDEOS_RAW):
    video_id = video_data['id']
    node_pos_percent = NODE_POSITIONS.get(video_id)
    if not node_pos_percent:
        continue # Skip if no position defined for this video

    # Add position data (as percentages) to the video dictionary
    video_data['pos_top'] = node_pos_percent['top']
    video_data['pos_left'] = node_pos_percent['left']

    # Calculate path data leading TO this current node FROM the previous node
    if prev_node_pos_data:
        # Convert percentages to approximate pixels for calculation
        x1_px = prev_node_pos_data['left'] / 100 * MAP_AREA_WIDTH_PX
        y1_px = prev_node_pos_data['top'] / 100 * MAP_AREA_HEIGHT_PX
        x2_px = video_data['pos_left'] / 100 * MAP_AREA_WIDTH_PX
        y2_px = video_data['pos_top'] / 100 * MAP_AREA_HEIGHT_PX

        dx = x2_px - x1_px
        dy = y2_px - y1_px

        distance = math.sqrt(dx**2 + dy**2)
        angle_rad = math.atan2(dy, dx) # Angle in radians

        # Store path data in the *current* video, which represents the segment *before* it
        video_data['path_data'] = {
            'top': f"{prev_node_pos_data['top']}%", # Path starts at previous node's top
            'left': f"{prev_node_pos_data['left']}%", # Path starts at previous node's left
            'width': f"{distance}px", # Width in pixels
            'transform': f"rotate({math.degrees(angle_rad)}deg)" # CSS rotate expects degrees
        }
    else:
        video_data['path_data'] = None # No path for the very first node

    VIDEOS.append(video_data)
    # Update prev_node_pos_data for the next iteration
    prev_node_pos_data = {'top': video_data['pos_top'], 'left': video_data['pos_left']}


# Quizzes (Video ID -> List of Questions)
# Each question: 'question', 'options' (list), 'correct_answer_index' (0-indexed), 'points', 'reason'
QUIZZES = {
    "v1": [
        {"question": "What is the primary benefit of rainwater collection?", "options": ["Reduced water bills", "Increased garden pests", "More cloudy days", "Better internet speed"], "correct_answer_index": 0, "points": 20, "reason": "Rainwater collection significantly reduces the need for municipal or well water, directly lowering utility costs."},
        {"question": "What is a common component of a basic rainwater system?", "options": ["Solar panels", "Downspout", "Microwave", "Treadmill"], "correct_answer_index": 1, "points": 20, "reason": "A downspout is crucial for directing water from the roof (collection surface) to the storage system."},
        {"question": "Why is rainwater harvesting considered sustainable?", "options": ["It uses more energy", "It depletes groundwater", "It utilizes a renewable resource", "It requires constant human intervention"], "correct_answer_index": 2, "points": 20, "reason": "Rainwater is a naturally replenished resource, making its use sustainable as it doesn't deplete finite resources."},
    ],
    "v2": [
        {"question": "What is the first step in harvesting rainwater?", "options": ["Filtering", "Collecting from a surface", "Storing in a tank", "Drinking it"], "correct_answer_index": 1, "points": 20, "reason": "The initial step is always to collect water, typically from a roof or other suitable surface."},
        {"question": "Which of these is NOT typically part of an introduction to rainwater harvesting?", "options": ["System sizing", "Components overview", "Basic benefits", "Advanced purification techniques"], "correct_answer_index": 3, "points": 20, "reason": "Advanced purification techniques are usually covered in more in-depth or specialized modules, not basic introductions."},
        {"question": "What is a common use for harvested rainwater in a household?", "options": ["Cooking", "Showering", "Toilet flushing", "Drinking without treatment"], "correct_answer_index": 2, "points": 20, "reason": "Toilet flushing is a common and excellent use for untreated or minimally treated rainwater, reducing potable water consumption."},
    ],
    "v3": [
        {"question": "What is the main purpose of a first flush diverter?", "options": ["To increase water pressure", "To divert initial dirty rainwater", "To cool the water", "To attract insects"], "correct_answer_index": 1, "points": 20, "reason": "A first flush diverter prevents the initial, most contaminated rainwater (carrying debris from the roof) from entering the main storage tank."},
        {"question": "When does the first flush occur?", "options": ["During the middle of a storm", "After a prolonged dry spell", "At the very beginning of a rain event", "When the tank is full"], "correct_answer_index": 2, "points": 20, "reason": "The 'first flush' refers to the first few minutes of a rainfall event, when the most impurities are washed off collection surfaces."},
        {"question": "Why is it important to divert the first flush?", "options": ["To save space in the tank", "To improve water quality", "To reduce the amount of collected water", "To make the system more complex"], "correct_answer_index": 1, "points": 20, "reason": "Diverting the first flush significantly improves the overall quality of the water collected in the storage tank."},
    ],
     "v4": [
        {"question": "What is crucial for making rainwater safe to drink?", "options": ["No filtration at all", "Only basic screening", "Proper filtration and purification", "Adding sugar"], "correct_answer_index": 2, "points": 20, "reason": "For drinking, rainwater requires thorough filtration to remove particulates and purification (like boiling or UV) to eliminate pathogens."},
        {"question": "What contaminants might be found in raw rainwater?", "options": ["Pure oxygen", "Roof debris, dust, bird droppings", "Salt", "Distilled water"], "correct_answer_index": 1, "points": 20, "reason": "Raw rainwater can pick up various contaminants from the atmosphere and collection surfaces, including organic matter, dust, and animal waste."},
        {"question": "Which method is commonly used for purifying drinking water from rainwater?", "options": ["Boiling", "Freezing", "Microwaving", "Shaking vigorously"], "correct_answer_index": 0, "points": 20, "reason": "Boiling water for at least one minute is a reliable method to kill most bacteria and viruses, making it safe for consumption."},
    ],
    "v5": [
        {"question": "What type of pipe is often used for first flush diverters?", "options": ["Electrical conduit", "PVC pipe", "Garden hose", "Metal rebar"], "correct_answer_index": 1, "points": 20, "reason": "PVC (Polyvinyl Chloride) pipes are commonly used due to their durability, cost-effectiveness, and ease of installation for water systems."},
        {"question": "Where is a first flush diverter typically installed?", "options": ["Inside the storage tank", "At the very top of the roof", "Between the gutter downspout and the storage tank", "Underground, far from the house"], "correct_answer_index": 2, "points": 20, "reason": "It's installed in the downspout system before the water enters the main storage, allowing the initial flow to be diverted."},
        {"question": "What is a common safety measure during installation?", "options": ["Working alone", "Wearing a blindfold", "Using proper tools and fall protection", "Installing during a thunderstorm"], "correct_answer_index": 2, "points": 20, "reason": "Safety precautions like using correct tools and fall protection are essential, especially when working on roofs or with heavy equipment."},
    ],
    "v6": [
        {"question": "What does a 'DIY Home System Tour' typically showcase?", "options": ["Professional installation only", "Pre-built commercial systems", "Custom-built, homeowner-installed systems", "Fantasy systems"], "correct_answer_index": 2, "points": 20, "reason": "DIY tours focus on practical, often budget-friendly systems built and managed by homeowners themselves."},
        {"question": "What is a common component highlighted in a home tour?", "options": ["An electric car charger", "A storage tank", "A swimming pool heater", "A satellite dish"], "correct_answer_index": 1, "points": 20, "reason": "The storage tank is a central and visually significant component of any rainwater harvesting system, often a key focus in tours."},
        {"question": "What can be learned from a DIY system tour?", "options": ["How to break the system", "Specific design choices and challenges", "The history of plumbing", "Advanced quantum physics"], "correct_answer_index": 1, "points": 20, "reason": "DIY tours offer insights into practical design considerations, modifications, and solutions to common challenges faced by homeowners."},
    ],
    "v7": [
        {"question": "What might an upgrade to a large system involve?", "options": ["Reducing tank capacity", "Adding more collection surfaces or storage", "Removing all filters", "Switching to bottled water"], "correct_answer_index": 1, "points": 20, "reason": "Upgrading often means expanding capacity to collect more water or increasing storage to meet higher demand."},
        {"question": "Why would someone upgrade a large rainwater harvesting system?", "options": ["To collect less water", "To improve efficiency or meet higher demand", "To make it less reliable", "To attract more birds"], "correct_answer_index": 1, "points": 20, "reason": "Upgrades are typically motivated by a need for more water, better performance, or adapting to changing water demands."},
        {"question": "What should be considered when planning a system upgrade?", "options": ["Ignoring existing infrastructure", "Only focusing on aesthetics", "Budget, demand, and integration with current setup", "Completely random additions"], "correct_answer_index": 2, "points": 20, "reason": "Effective planning requires assessing financial resources, water needs, and how new components will fit with existing infrastructure."},
    ],
    "v8": [
        {"question": "What is a primary challenge of living on rainwater in a desert?", "options": ["Too much rain", "Limited rainfall and high evaporation", "No need for water", "Excessive humidity"], "correct_answer_index": 1, "points": 20, "reason": "Deserts are characterized by scarce rainfall and high temperatures, leading to significant water loss through evaporation, making collection challenging."},
        {"question": "What strategy is crucial for rainwater harvesting in arid climates?", "options": ["Using tiny collection areas", "Maximizing collection surfaces and storage", "Wasting water frequently", "Ignoring forecasts"], "correct_answer_index": 1, "points": 20, "reason": "In arid regions, it's vital to capture as much rainfall as possible from large surfaces and store it effectively to last through dry periods."},
        {"question": "Why is careful management important in a desert environment?", "options": ["Water is abundant", "Water is a scarce and precious resource", "It's a fun hobby", "To prove a point"], "correct_answer_index": 1, "points": 20, "reason": "Water is extremely limited in deserts, so every drop must be managed wisely to ensure survival and sustainability."},
    ],
    "v9": [
        {"question": "What material are many water storage tanks made from?", "options": ["Glass", "Wood", "Polyethylene", "Cardboard"], "correct_answer_index": 2, "points": 20, "reason": "Polyethylene is a common material for water tanks due to its durability, resistance to corrosion, and food-grade safety for water storage."},
        {"question": "What tool might be used to prepare a cracked polyethylene tank for repair?", "options": ["A hammer", "A heat gun or plastic welder", "A spoon", "A paperclip"], "correct_answer_index": 1, "points": 20, "reason": "Plastic welding or using a heat gun with filler material is the appropriate method for repairing cracks in polyethylene tanks."},
        {"question": "What is a key step in ensuring a successful tank repair?", "options": ["Leaving the crack dirty", "Cleaning and drying the area thoroughly", "Applying repair material blindly", "Ignoring manufacturer instructions"], "correct_answer_index": 1, "points": 20, "reason": "A clean, dry surface is essential for proper adhesion and a long-lasting repair when working with adhesives or plastic welding."},
    ],
    "v10": [
        {"question": "What is the main function of a pump in a rainwater system?", "options": ["To filter the water", "To add chemicals to the water", "To pressurize and distribute water", "To cool the water"], "correct_answer_index": 2, "points": 20, "reason": "Pumps are used to move water from the storage tank to where it's needed, often at a specific pressure for household use."},
        {"question": "What kind of power source do most rainwater pumps use?", "options": ["Manual hand crank", "Electricity", "Solar power only", "Wind power"], "correct_answer_index": 1, "points": 20, "reason": "While solar pumps exist, most common rainwater systems utilize electric pumps for consistent power and performance."},
        {"question": "What needs to be considered when selecting a pump?", "options": ["Its color", "Flow rate and pressure requirements", "The brand of your car", "Its weight"], "correct_answer_index": 1, "points": 20, "reason": "The pump must be chosen based on the desired water flow (how much water per minute) and pressure (how strong the water comes out) for the intended application."},
    ],
}

BADGES = [
    {"id": "b1", "name": "Raindrop Rookie", "description": "Completed your first video.", "unlock_criteria": {"type": "video_completion", "video_id": "v1"}},
    {"id": "b2", "name": "First Flush Friend", "description": "Understood first flush diverters (v3 & v5).", "unlock_criteria": {"type": "multiple_video_completion", "video_ids": ["v3", "v5"]}},
    {"id": "b3", "name": "Clean Water Champion", "description": "Mastered making rainwater safe to drink (v4 quiz 100%).", "unlock_criteria": {"type": "quiz_score_100", "video_id": "v4"}},
    {"id": "b4", "name": "System Surveyor", "description": "Explored home and large system upgrades (v6 & v7).", "unlock_criteria": {"type": "multiple_video_completion", "video_ids": ["v6", "v7"]}},
    {"id": "b5", "name": "Desert Harvester", "description": "Learned about harvesting in arid climates (v8).", "unlock_criteria": {"type": "video_completion", "video_id": "v8"}},
    {"id": "b6", "name": "HydroHero Apprentice", "description": "Reached 500 total points.", "unlock_criteria": {"type": "total_points_threshold", "threshold": 500}},
    {"id": "b7", "name": "Ultimate HydroHero", "description": "Completed all videos and quizzes.", "unlock_criteria": {"type": "all_videos_completed_and_quizzed"}},
]

# --- In-Memory User Progress State (Global for single-user session) ---
# Using session for user_progress to make it somewhat persistent per user session
# Initialize if not present in session
def get_user_progress():
    if 'user_progress' not in session:
        session['user_progress'] = {
            "points": 0,
            "completed_videos": [],
            "video_quiz_scores": {},
            "unlocked_badges": []
        }
    return session['user_progress']

# --- Helper Functions ---

def get_video_by_id(video_id):
    """Retrieves video details by ID from the processed VIDEOS list."""
    return next((video for video in VIDEOS if video["id"] == video_id), None)

def get_quiz_by_video_id(video_id):
    """Retrieves quiz questions by video ID."""
    return QUIZZES.get(video_id)

def evaluate_badges():
    """Checks user progress against badge criteria and unlocks new badges."""
    user_progress = get_user_progress()
    current_unlocked_badge_ids = {badge['id'] for badge in user_progress['unlocked_badges']}

    for badge in BADGES:
        if badge['id'] not in current_unlocked_badge_ids:
            unlocked = False
            criteria = badge['unlock_criteria']

            if criteria['type'] == 'video_completion':
                if criteria['video_id'] in user_progress['completed_videos']:
                    unlocked = True
            elif criteria['type'] == 'multiple_video_completion':
                if all(vid in user_progress['completed_videos'] for vid in criteria['video_ids']):
                    unlocked = True
            elif criteria['type'] == 'quiz_score_100':
                video_id = criteria['video_id']
                if video_id in user_progress['video_quiz_scores']:
                    quiz_questions = get_quiz_by_video_id(video_id)
                    if quiz_questions:
                        max_score = sum(q['points'] for q in quiz_questions)
                        if user_progress['video_quiz_scores'][video_id] == max_score:
                            unlocked = True
            elif criteria['type'] == 'total_points_threshold':
                if user_progress['points'] >= criteria['threshold']:
                    unlocked = True
            elif criteria['type'] == 'all_videos_completed_and_quizzed':
                if len(user_progress['completed_videos']) == len(VIDEOS) and \
                   len(user_progress['video_quiz_scores']) == len(VIDEOS) and \
                   all(score is not None for score in user_progress['video_quiz_scores'].values()):
                    unlocked = True

            if unlocked:
                user_progress['unlocked_badges'].append(badge)
                flash(f"πŸŽ‰ New Badge Unlocked: {badge['name']}!", 'success')
                session['user_progress'] = user_progress # Update session after modification

# --- Flask Routes ---

@app.route('/')
def landing():
    """Renders the game's landing page."""
    return render_template('landing.html')

@app.route('/dashboard')
def dashboard():
    """Renders the main dashboard showing videos, progress, and badges."""
    user_progress = get_user_progress()
    all_videos_and_quizzes_completed = False
    if 'b7' in {badge['id'] for badge in user_progress['unlocked_badges']}:
        all_videos_and_quizzes_completed = True

    return render_template('dashboard.html',
                           videos=VIDEOS, # Pass the augmented VIDEOS list
                           user_progress=user_progress,
                           total_videos=len(VIDEOS),
                           badges=BADGES,
                           all_videos_and_quizzes_completed=all_videos_and_quizzes_completed,
                           username=session.get('username', ''))

@app.route('/start_level/<video_id>')
def start_level(video_id):
    """Displays the video for the user to watch."""
    user_progress = get_user_progress()
    video = get_video_by_id(video_id)

    if not video:
        flash("Video not found!", 'error')
        return redirect(url_for('dashboard'))

    # Check if the previous video is completed for sequential access (unless it's the first video)
    video_index = next((i for i, v in enumerate(VIDEOS) if v['id'] == video_id), -1)
    if video_index > 0 and VIDEOS[video_index - 1]['id'] not in user_progress['completed_videos']:
        flash("Please complete the previous video first!", 'warning')
        return redirect(url_for('dashboard'))

    return render_template('start_level.html', video=video)


@app.route('/complete_video/<video_id>')
def complete_video(video_id):
    """Marks a video as complete and redirects to its quiz."""
    user_progress = get_user_progress()
    video = get_video_by_id(video_id)

    if not video:
        flash("Video not found!", 'error')
        return redirect(url_for('dashboard'))

    if video_id not in user_progress['completed_videos']:
        user_progress['completed_videos'].append(video_id)
        user_progress['points'] += video['base_points']
        flash(f"Video '{video['title']}' completed! +{video['base_points']} points.", 'info')
        session['user_progress'] = user_progress # Update session after modification
        evaluate_badges()

    return redirect(url_for('quiz', video_id=video_id))

@app.route('/quiz/<video_id>', methods=['GET', 'POST'])
def quiz(video_id):
    """Displays and processes quizzes for completed videos."""
    user_progress = get_user_progress()
    video = get_video_by_id(video_id)
    quiz_questions = get_quiz_by_video_id(video_id)

    if not video or not quiz_questions:
        flash("Quiz or video not found!", 'error')
        return redirect(url_for('dashboard'))

    if video_id not in user_progress['completed_videos']:
        flash("Please complete the video before taking the quiz.", 'warning')
        return redirect(url_for('dashboard'))

    # If quiz already taken, and it's a GET request, redirect to dashboard
    # The user can't retake a quiz, so no need to show old results on GET
    if video_id in user_progress['video_quiz_scores'] and request.method == 'GET':
        flash(f"You already completed the quiz for '{video['title']}'. Your score: {user_progress['video_quiz_scores'][video_id]} points.", 'info')
        return redirect(url_for('dashboard'))

    if request.method == 'POST':
        user_score = 0
        correct_answers_count = 0
        quiz_feedback = [] # To store feedback for each question

        for i, question_data in enumerate(quiz_questions):
            user_answer_index_str = request.form.get(f'question_{i}')
            user_answer_index = None
            is_correct = False
            points_for_this_question = 0
            user_selected_option_text = "No answer" # Default if nothing selected

            if user_answer_index_str:
                try:
                    user_answer_index = int(user_answer_index_str)
                    user_selected_option_text = question_data['options'][user_answer_index]
                    if user_answer_index == question_data['correct_answer_index']:
                        is_correct = True
                        points_for_this_question = question_data['points']
                        user_score += points_for_this_question
                        correct_answers_count += 1
                except ValueError:
                    pass # user_answer_index remains None, user_selected_option_text is "No answer"

            correct_option_text = question_data['options'][question_data['correct_answer_index']]
            reason_text = question_data.get('reason', 'No specific reason provided for this question.') # Get reason, provide default

            quiz_feedback.append({
                'question': question_data['question'],
                'user_answer_index': user_answer_index,
                'user_selected_option': user_selected_option_text,
                'correct_answer_index': question_data['correct_answer_index'],
                'correct_option': correct_option_text,
                'is_correct': is_correct,
                'points_earned': points_for_this_question,
                'reason': reason_text
            })

        user_progress['points'] += user_score
        user_progress['video_quiz_scores'][video_id] = user_score
        flash(f"Quiz completed for '{video['title']}': You scored {user_score} points ({correct_answers_count}/{len(quiz_questions)} correct)!", 'success')
        session['user_progress'] = user_progress # Update session after modification
        evaluate_badges()

        # Render the quiz page again, but this time with feedback
        return render_template('quiz.html', video=video, quiz_questions=quiz_questions,
                               quiz_feedback=quiz_feedback, user_score=user_score,
                               total_quiz_points=sum(q['points'] for q in quiz_questions),
                               enumerate=enumerate, quiz_submitted=True)

    # For GET request (displaying quiz for the first time)
    return render_template('quiz.html', video=video, quiz_questions=quiz_questions, enumerate=enumerate, quiz_submitted=False)

@app.route('/set_username', methods=['POST'])
def set_username():
    """Sets the username in the session."""
    if request.method == 'POST':
        username = request.form.get('username')
        if username:
            session['username'] = username
            flash(f"Your name has been set to {username}!", 'info')
        else:
            flash("Please enter a valid name.", 'warning')
    return redirect(url_for('dashboard'))

@app.route('/certificate')
def certificate():
    """Generates and displays the certificate of completion."""
    user_progress = get_user_progress()
    # Check if the 'Ultimate HydroHero' badge (b7) is unlocked
    all_completed = 'b7' in {badge['id'] for badge in user_progress['unlocked_badges']}

    if not all_completed:
        flash("You must complete all videos and quizzes to earn a certificate!", 'error')
        return redirect(url_for('dashboard'))

    username = session.get('username', 'Rainwater Hero') # Default name if not set
    total_points = user_progress['points']

    # Custom date filter for Jinja2
    from datetime import datetime
    app.jinja_env.filters['date'] = lambda date_str, fmt: datetime.now().strftime(fmt)

    return render_template('certificate.html', username=username, total_points=total_points)

# --- Run the App ---
if __name__ == '__main__':
    app.run(debug=True)