File size: 27,910 Bytes
d13b59f
 
 
 
 
 
 
 
 
 
 
 
 
0b60c42
d13b59f
 
0b60c42
 
 
 
 
 
 
 
 
 
 
 
 
 
d13b59f
0b60c42
 
 
 
 
d13b59f
0b60c42
 
 
 
 
 
 
d13b59f
0b60c42
 
 
 
 
 
 
 
 
d13b59f
0b60c42
 
d13b59f
0b60c42
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
 
0b60c42
d13b59f
 
0b60c42
 
 
 
 
 
d13b59f
 
 
 
 
 
 
0b60c42
 
 
d13b59f
 
 
 
 
 
 
0b60c42
d13b59f
 
0b60c42
 
 
d13b59f
 
 
 
 
 
 
 
 
0b60c42
 
 
d13b59f
 
 
 
 
 
 
 
 
 
0b60c42
 
 
 
 
d13b59f
 
 
0b60c42
 
d13b59f
0b60c42
 
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
 
0b60c42
d13b59f
 
 
0b60c42
 
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
0b60c42
 
d13b59f
0b60c42
d13b59f
0b60c42
 
 
 
 
d13b59f
 
0b60c42
 
 
 
 
 
 
 
d13b59f
0b60c42
 
 
 
 
 
 
 
 
 
d13b59f
 
0b60c42
d13b59f
 
 
0b60c42
 
 
 
 
 
 
 
 
 
 
d13b59f
 
 
0b60c42
d13b59f
0b60c42
 
 
 
 
d13b59f
 
0b60c42
 
d13b59f
0b60c42
 
 
d13b59f
 
 
0b60c42
d13b59f
 
 
0b60c42
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
0b60c42
 
 
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
 
0b60c42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
 
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
 
 
 
0b60c42
 
d13b59f
 
 
 
0b60c42
 
 
 
d13b59f
0b60c42
d13b59f
 
 
 
 
0b60c42
d13b59f
0b60c42
d13b59f
0b60c42
 
 
d13b59f
 
 
 
 
 
0b60c42
 
d13b59f
0b60c42
 
 
d13b59f
0b60c42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d13b59f
 
 
 
 
 
0b60c42
 
 
 
 
 
 
 
 
 
 
d13b59f
0b60c42
 
 
 
 
 
 
 
 
 
 
d13b59f
 
 
0b60c42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d13b59f
0b60c42
 
 
 
d13b59f
0b60c42
d13b59f
 
0b60c42
d13b59f
 
 
 
 
0b60c42
 
 
 
 
 
d13b59f
 
 
 
0b60c42
d13b59f
 
0b60c42
d13b59f
0b60c42
 
 
 
 
 
 
 
d13b59f
 
 
 
0b60c42
d13b59f
 
0b60c42
d13b59f
 
 
 
 
 
0b60c42
 
 
d13b59f
 
 
 
0b60c42
d13b59f
 
0b60c42
d13b59f
 
 
 
 
 
0b60c42
 
 
d13b59f
 
 
 
0b60c42
d13b59f
 
0b60c42
d13b59f
 
 
 
 
 
0b60c42
 
 
d13b59f
 
 
 
 
 
 
 
 
 
 
 
 
0b60c42
 
d13b59f
 
0b60c42
d13b59f
 
 
 
 
 
 
 
 
0b60c42
d13b59f
 
0b60c42
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# player_interface.py
import gradio as gr
import pandas as pd
from quiz_manager import quiz_manager # Assuming quiz_manager exists and is accessible

class PlayerInterface:
    def __init__(self):
        self.current_game_pin = None
        self.current_question_index = -1
        self.current_quiz_data = None
        self.player_name = None
        self.total_questions = 0

    # Modified join_game to return updates for the Group visibility
    def join_game(self, game_pin, player_name):
        """Validate the game pin and allow player to join."""
        print(f"Attempting to join game: Pin={game_pin}, Player={player_name}")
        if not game_pin or not game_pin.strip() or not player_name or not player_name.strip():
             print("JOIN_GAME: Empty pin or name")
             # Return outputs for staying on the join screen
             return (
                "Please enter both Game Pin and your Name.", # Join Status
                gr.update(visible=True), # Show Join Group
                gr.update(visible=False), # Hide Game Area
                gr.update(visible=False), # Hide Leaderboard Area
                gr.update(choices=[], visible=False, value=None), # Hide Question Dropdown
                gr.update(visible=False), # Hide Next Button Row
                gr.update(value="") # Clear Question Display
             )

        try:
            # Check if quiz exists
            quiz = quiz_manager.get_quiz(game_pin.strip())
            if not quiz:
                print(f"JOIN_GAME: Invalid pin: {game_pin}")
                # Return outputs for staying on the join screen with error
                return (
                    f"Invalid game pin: {game_pin.strip()}. Please try again.",
                    gr.update(visible=True), # Show Join Group
                    gr.update(visible=False), # Hide Game Area
                    gr.update(visible=False), # Hide Leaderboard Area
                    gr.update(choices=[], visible=False, value=None), # Hide Question Dropdown
                    gr.update(visible=False), # Hide Next Button Row
                    gr.update(value="") # Clear Question Display
                )

            self.current_game_pin = game_pin.strip()
            self.player_name = player_name.strip()
            self.current_quiz_data = quiz_manager.quizzes.get(self.current_game_pin)

            # Initialize player score if they are new to this quiz
            quiz_manager.initialize_player_score(self.current_game_pin, self.player_name)

            self.total_questions = len(self.current_quiz_data.get('questions', []))
            question_numbers = list(range(1, self.total_questions + 1))
            print(f"JOIN_GAME: Success for {self.player_name} in {self.current_game_pin}. Total questions: {self.total_questions}")


            # Return updates to hide Join Group, show Game Area, show Leaderboard Area, and update dropdown
            return (
                f"Joined game {self.current_game_pin} successfully, {self.player_name}!", # Join Status
                gr.update(visible=False), # Hide Join Group
                gr.update(visible=True),  # Show Game Area
                gr.update(visible=True),   # Show Leaderboard Area
                 # Update choices, make visible if questions exist, reset value
                gr.update(choices=question_numbers, visible=self.total_questions > 0, value=None),
                gr.update(visible=self.total_questions > 0),  # Show Next Button Row only if questions exist
                gr.update(value="Click 'Start Quiz' to begin.") # Set initial text for Question Display
            )
        except Exception as e:
            print(f"JOIN_GAME: Error - {str(e)}")
             # Return updates for all relevant components on error, stay on join screen
            return (
                f"Error joining game: {str(e)}",
                gr.update(visible=True), # Show Join Group
                gr.update(visible=False), # Hide Game Area
                gr.update(visible=False), # Hide Leaderboard Area
                gr.update(choices=[], visible=False, value=None), # Hide Question Dropdown
                gr.update(visible=False), # Hide Next Button Row
                gr.update(value="") # Clear Question Display
            )


    def show_question(self):
        """Prepare updates for displaying the current question."""
        print("SHOW_QUESTION: Called")
        # Prepare default updates for components
        default_options_update = gr.update(choices=[], value=None, visible=False)
        default_prompt_label_update = gr.update(value="", visible=False)
        default_question_area_update = gr.update(visible=True) # Assume area itself is visible when showing a question
        default_submit_btn_update = gr.update(visible=True)
        default_answer_output_update = gr.update(value="")


        if not self.current_quiz_data or self.current_question_index == -1:
            print("SHOW_QUESTION: No quiz data or invalid index")
            return (
                "Error: Could not load question.",
                default_options_update,
                default_prompt_label_update,
                gr.update(visible=False), # Hide question area if no data
                gr.update(visible=False), # Hide submit button if no data
                gr.update(value="")
            )

        questions = self.current_quiz_data.get('questions', [])
        if not questions or self.current_question_index < 0 or self.current_question_index >= len(questions):
             print("SHOW_QUESTION: Invalid question data or index out of bounds")
             return (
                "Error loading question data.",
                default_options_update,
                default_prompt_label_update,
                gr.update(visible=False),
                gr.update(visible=False),
                gr.update(value="")
             )


        question_data = questions[self.current_question_index]
        options = question_data.get('options', [])
        question_text = question_data.get('text', 'Error loading question text')
        print(f"SHOW_QUESTION: Displaying question - index: {self.current_question_index}, text: {question_text}")

        # Return specific updates for the current question
        return (
            f"**Question {self.current_question_index + 1}/{self.total_questions}:** {question_text}", # Update question_display value with bolding
            gr.update(choices=options, value=None, label="Answer Choices", visible=True), # Update options_radio, make visible
            gr.update(value="Select your answer:", visible=True), # Update prompt label, make visible
            gr.update(visible=True),  # Make question_area visible
            gr.update(visible=True),  # Make submit_btn visible
            gr.update(value="")       # Clear answer_output value
        )

    # Modified methods to return updates for dropdown and next button row visibility
    # next_step returns 8 outputs
    def next_step(self):
        """Starts the quiz, showing the first question."""
        print("NEXT_STEP: Called (Start Quiz)")
        if self.current_quiz_data and self.total_questions > 0:
            self.current_question_index = 0
            question_updates = self.show_question() # 6 outputs
            print(f"NEXT_STEP: Showing question - index: {self.current_question_index}")
            return (
                *question_updates, # Unpack the 6 updates
                 gr.update(visible=False), # Hide Next Button Row
                 gr.update(value=self.current_question_index + 1) # Update dropdown value
            )
        else:
            print("NEXT_STEP: No questions available")
            # Return default updates if no questions, hide game elements
            return (
                "No questions available for this quiz.", # Question Display (1)
                gr.update(choices=[], value=None, label="Answer Choices", visible=False), # Options Radio (2)
                gr.update(value="", visible=False), # Prompt Label (3)
                gr.update(visible=False), # Question Area (4)
                gr.update(visible=False), # Submit Button (5)
                gr.update(value=""), # Answer Output (6)
                gr.update(visible=False), # Hide Next Button Row (7)
                gr.update(value=None) # Clear dropdown value (8)
            )


    # next_question returns 8 outputs
    def next_question(self):
        """Move to the next question."""
        print("NEXT_QUESTION: Called")
        if not self.current_quiz_data or not self.current_game_pin or not self.player_name:
            print("NEXT_QUESTION: No quiz data or player context")
            return (
                "Please join a game first.", # Question Display (1)
                gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=True), # Keep Next Button Row visible if no quiz data (7)
                gr.update(value=None) # Clear dropdown value (8)
            )
        questions = self.current_quiz_data.get('questions', [])
        if self.current_question_index < len(questions) - 1:
            self.current_question_index += 1
            question_updates = self.show_question() # 6 outputs
            print(f"NEXT_QUESTION: Showing question - index: {self.current_question_index}")
            return (
                *question_updates,
                 gr.update(visible=False), # Keep Next Button Row hidden (7)
                 gr.update(value=self.current_question_index + 1) # Update dropdown value (8)
            )
        else:
            print("NEXT_QUESTION: Quiz finished")
            # Reset instance variables
            self.current_question_index = -1
            self.current_quiz_data = None
            self.current_game_pin = None
            self.player_name = None
            self.total_questions = 0

            # Return updates for quiz finished state, hide game elements, show join box needs separate trigger
            return (
                "Quiz finished! You can join another game.", # Question Display (1)
                 gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=False), # Hide Next Button Row (7)
                gr.update(value=None) # Clear dropdown value (8)
                 # Note: Join Box, Game Area, Leaderboard Area visibility needs to be handled by a separate action or logic
                 # A 'Play Again' or 'Join New Game' button in the finished state might be better UI.
            )

    # previous_question returns 8 outputs
    def previous_question(self):
        """Move to the previous question."""
        print("PREVIOUS_QUESTION: Called")
        if not self.current_quiz_data or not self.current_game_pin or not self.player_name:
             print("PREVIOUS_QUESTION: No quiz data or player context")
             return (
                "Please join a game first.", # Question Display (1)
                gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=True), # Keep Next Button Row visible if no quiz data (7)
                gr.update(value=None) # Clear dropdown value (8)
            )
        if self.current_question_index > 0:
            self.current_question_index -= 1
            question_updates = self.show_question() # 6 outputs
            print(f"PREVIOUS_QUESTION: Showing question - index: {self.current_question_index}")
            return (
                 *question_updates,
                 gr.update(visible=False), # Keep Next Button Row hidden (7)
                 gr.update(value=self.current_question_index + 1) # Update dropdown value (8)
            )
        else:
            print("PREVIOUS_QUESTION: First question")
            # show_question already sets visibility for elements in question_area
            question_updates = self.show_question() # 6 outputs for current question (index 0)
            return (
                *question_updates, # Includes showing question area elements
                gr.update(visible=False), # Keep Next Button Row hidden (7)
                gr.update(value=self.current_question_index + 1) # Keep dropdown value at 1 (8)
            )


    # select_question_by_number returns 8 outputs
    def select_question_by_number(self, question_number):
        """Select a specific question by its number."""
        print(f"SELECT_QUESTION_BY_NUMBER: Called with {question_number}")
        # Handle case where question_number might be None from dropdown reset or initial state
        if question_number is None:
             print("SELECT_QUESTION_BY_NUMBER: question_number is None")
             # Return outputs to clear question area, hide game elements
             return (
                "", # Clear question text (1)
                gr.update(choices=[], value=None, label="Answer Choices", visible=False), # Clear options (2)
                gr.update(value="", visible=False), # Clear prompt label (3)
                gr.update(visible=False), # Hide question area (4)
                gr.update(visible=False), # Hide submit button (5)
                gr.update(value=""), # Clear answer output (6)
                gr.update(visible=False), # Hide Next Button Row (7)
                gr.update(value=None) # Clear dropdown value (8)
            )

        if not self.current_quiz_data or not self.current_game_pin or not self.player_name:
            print("SELECT_QUESTION_BY_NUMBER: No quiz data or player context")
            # Return default updates, hide game elements
            return (
                "Please join a game first.", # (1)
                gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=False), # (7)
                gr.update(value=None) # (8)
            )

        questions = self.current_quiz_data.get('questions', [])
        # Convert question_number to integer index
        try:
            index = int(question_number) - 1
        except (ValueError, TypeError):
             print("SELECT_QUESTION_BY_NUMBER: Invalid number input")
             return (
                "Invalid input for question number.", # (1)
                 gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=False), # (7)
                gr.update(value=None) # (8)
            )


        if 0 <= index < len(questions):
            self.current_question_index = index
            question_updates = self.show_question() # 6 outputs
            print(f"SELECT_QUESTION_BY_NUMBER: Showing question - index: {self.current_question_index}")
            # Append updates for the dropdown (keep value) and keep next button row hidden
            return (
                 *question_updates,
                 gr.update(visible=False), # Keep Next Button Row hidden (7)
                 gr.update(value=self.current_question_index + 1) # Keep dropdown value (8)
            )
        else:
            print("SELECT_QUESTION_BY_NUMBER: Invalid question number")
            # Return updates for invalid number, hide game elements
            return (
                "Invalid question number.", # (1)
                 gr.update(choices=[], value=None, label="Answer Choices", visible=False), # (2)
                gr.update(value="", visible=False), # (3)
                gr.update(visible=False), # (4)
                gr.update(visible=False), # (5)
                gr.update(value=""), # (6)
                gr.update(visible=False), # (7)
                gr.update(value=None) # (8)
            )


    def submit_answer(self, selected_option):
        """Submit the player's answer for the current question."""
        if not self.current_quiz_data or self.current_question_index == -1 or not self.current_game_pin or not self.player_name:
            return "Please join a game and select a question first."
         # Ensure selected_option is not None if the user submits without selecting
        if selected_option is None:
             return "Please select an answer."

        # Fetch quiz data again to ensure it's fresh before checking answer
        quiz = quiz_manager.get_quiz(self.current_game_pin)
        if not quiz or 'questions' not in quiz or self.current_question_index < 0 or self.current_question_index >= len(quiz['questions']):
            return "Error submitting answer: Could not retrieve current question data."

        current_question = quiz['questions'][self.current_question_index]
        correct_answer = current_question.get('correct_answer')
        # Handle cases where correct_answer might be missing
        if correct_answer is None:
             return "Error submitting answer: Correct answer not defined for this question."

        is_correct = selected_option.strip() == correct_answer.strip()

        try:
            quiz_manager.record_answer(self.current_game_pin, self.player_name, is_correct)
        except Exception as e:
             return f"Error recording score: {str(e)}"


        return "Correct!" if is_correct else f"Incorrect. The correct answer was: {correct_answer}"

    def get_leaderboard(self):
        """Retrieve and display the leaderboard."""
        print("GET_LEADERBOARD: Called")
        # This method is called by the Refresh button *inside* the leaderboard_area.
        # It relies on self.current_game_pin being set by the join_game method.
        if not self.current_game_pin:
            # This case should ideally not happen if UI flow is correct, but for safety
            return "Please join a game first to view the leaderboard."

        try:
            leaderboard = quiz_manager.get_leaderboard(self.current_game_pin)

            if not leaderboard:
                print("GET_LEADERBOARD: No scores recorded")
                return "No scores recorded yet."

            leaderboard_str = "### Leaderboard:\n"
            # Sort leaderboard by score in descending order
            try:
                # get_leaderboard in quiz_manager returns a sorted list of (name, data) tuples
                sorted_leaderboard = leaderboard # It's already sorted by quiz_manager
                for i, (name, data) in enumerate(sorted_leaderboard, 1):
                    score = data.get('score', 0) # Safely get score from the dict
                    leaderboard_str += f"{i}. {name}: {score} points\n"
                print("GET_LEADERBOARD: Leaderboard generated")
                return leaderboard_str
            except (TypeError, IndexError, AttributeError) as e:
                # Catch potential errors if the list elements are not in the expected format
                print(f"GET_LEADERBOARD: Error processing leaderboard data - {str(e)}")
                # Display raw data for debugging
                return f"Error displaying leaderboard data: {str(e)}.\nRaw data: {leaderboard}"
        except Exception as e:
             return f"Error retrieving leaderboard: {str(e)}"


    def create_player_interface(self):
        """Create the Gradio interface for players with a step-by-step layout."""
        with gr.Blocks() as player_interface:
            gr.Markdown("## Quiz Player Dashboard")

            # --- Define all Components First ---

            # Join Game Section (Visible initially)
            # Replaced gr.Box with gr.Group
            join_group = gr.Group(visible=True)
            with join_group:
                gr.Markdown("### Join Game")
                with gr.Row():
                    game_pin_input = gr.Textbox(label="Enter Game Pin", scale=1)
                    player_name_input = gr.Textbox(label="Your Name", scale=1)
                    join_btn = gr.Button("Join Game", scale=1)
                join_output = gr.Textbox(label="Join Status", interactive=False)

            # Game Area (Hidden initially, shown after joining)
            game_area = gr.Column(visible=False)
            with game_area:
                gr.Markdown("### Play Quiz")
                # Initial Next Button (to start the quiz) - part of game_area but controlled separately
                next_button_row = gr.Row(visible=False) # Controlled visible/hidden
                with next_button_row:
                    next_btn = gr.Button("Start Quiz")

                # Question Dropdown (visibility controlled dynamically)
                question_dropdown = gr.Dropdown(label="Select Question", choices=[], visible=False, interactive=True)


                # Question Display Area (Hidden initially, shown after starting/selecting question)
                question_area = gr.Column(visible=False) # Controlled visible/hidden
                with question_area:
                    question_display = gr.Markdown(label="Question Text")
                    options_prompt_label = gr.Label(label="", visible=False)
                    options_radio = gr.Radio(label="Answer Choices", visible=False)
                    submit_btn = gr.Button("Submit Answer", visible=False)
                    answer_output = gr.Textbox(label="Answer Result", interactive=False)

                    # Navigation Buttons
                    with gr.Row():
                        prev_question_btn = gr.Button("Previous Question")
                        next_question_btn = gr.Button("Next Question")


            # Leaderboard Area (Hidden initially, shown after joining)
            leaderboard_area = gr.Column(visible=False) # Controlled visible/hidden
            with leaderboard_area:
                 gr.Markdown("### Current Leaderboard")
                 leaderboard_output = gr.Markdown(label="Leaderboard Results")
                 get_leaderboard_btn = gr.Button("Refresh Leaderboard")


            # --- Define all Interactions After Components ---

            # Join Game interaction
            # join_game returns 7 outputs: status, join_group_vis, game_area_vis, leaderboard_area_vis, dropdown_update, next_btn_row_vis, question_display_initial_text
            join_btn.click(
                fn=self.join_game,
                inputs=[game_pin_input, player_name_input],
                outputs=[
                    join_output,
                    join_group, # Update visibility of join group
                    game_area, # Update visibility of game area
                    leaderboard_area, # Update visibility of leaderboard area
                    question_dropdown, # Update dropdown choices/visibility
                    next_button_row, # Update visibility of next button row
                    question_display # Update initial text of question display
                ]
            )

            # Start Quiz button interaction (uses next_step to show the first question)
            # next_step returns 8 outputs: question_display, options_radio, prompt_label, question_area, submit_btn, answer_output, next_btn_row_vis, dropdown_value
            next_btn.click(
                fn=self.next_step,
                inputs=None, # next_step uses instance variables
                outputs=[
                    question_display,
                    options_radio,
                    options_prompt_label,
                    question_area,
                    submit_btn,
                    answer_output,
                    next_button_row,
                    question_dropdown # Update dropdown value
                ]
            )

            # Question Dropdown interaction
            # select_question_by_number returns 8 outputs: question_display, options_radio, prompt_label, question_area, submit_btn, answer_output, next_btn_row_vis, dropdown_value
            question_dropdown.change(
                fn=self.select_question_by_number,
                inputs=question_dropdown, # Pass the selected value (the question number)
                 outputs=[
                    question_display,
                    options_radio,
                    options_prompt_label,
                    question_area,
                    submit_btn,
                    answer_output,
                    next_button_row, # Ensure next button row is hidden after selection
                    question_dropdown # Keep dropdown value updated
                ]
            )

            # Next Question button interaction
            # next_question returns 8 outputs: question_display, options_radio, prompt_label, question_area, submit_btn, answer_output, next_btn_row_vis, dropdown_value
            next_question_btn.click(
                fn=self.next_question,
                inputs=None, # Uses instance variables
                 outputs=[
                    question_display,
                    options_radio,
                    options_prompt_label,
                    question_area,
                    submit_btn,
                    answer_output,
                    next_button_row,
                    question_dropdown # Update dropdown value
                ]
            )

            # Previous Question button interaction
            # previous_question returns 8 outputs: question_display, options_radio, prompt_label, question_area, submit_btn, answer_output, next_btn_row_vis, dropdown_value
            prev_question_btn.click(
                fn=self.previous_question,
                inputs=None, # Uses instance variables
                 outputs=[
                    question_display,
                    options_radio,
                    options_prompt_label,
                    question_area,
                    submit_btn,
                    answer_output,
                    next_button_row,
                    question_dropdown # Update dropdown value
                ]
            )

            # Submit Answer button interaction
            submit_btn.click(
                fn=self.submit_answer,
                inputs=options_radio, # Input is the selected value from the radio button
                outputs=answer_output, # Output is the result of the submission
            )

            # Refresh Leaderboard button interaction
            get_leaderboard_btn.click(
                fn=self.get_leaderboard,
                inputs=None, # Uses instance variables
                outputs=leaderboard_output, # Output is the formatted leaderboard string
            )

            # No specific load interaction needed here, join_game handles initial visibility.

        return player_interface

# --- How to use this class ---

# Create the PlayerInterface instance
player_interface_instance = PlayerInterface()

# To access the Gradio interface object:
# The app.py file will call this method to get the interface block
player_gradio_interface = player_interface_instance.create_player_interface()

# You can now use player_gradio_interface in your main app.py file.