Merge branch 'v4-extend-v2'
Browse files- README.md +9 -4
 - __pycache__/backend.cpython-312.pyc +0 -0
 - app.py +52 -33
 - assets/2024-10-21-17-07-42.png +0 -0
 - assets/2024-10-21-17-08-13.png +0 -0
 - assets/2024-10-21-17-09-15.png +0 -0
 - backend.py +23 -0
 - questions/CLF-C02-v1.json +0 -0
 - static/script.js +66 -20
 - static/style.css +26 -0
 - templates/client.html +21 -2
 - templates/host.html +21 -6
 
    	
        README.md
    CHANGED
    
    | 
         @@ -15,14 +15,19 @@ A Python-based real-time quiz application designed to mimic the functionality of 
     | 
|
| 15 | 
         
             
            ```bash
         
     | 
| 16 | 
         
             
            Real-Time-Quiz-Application/
         
     | 
| 17 | 
         
             
            β
         
     | 
| 
         | 
|
| 18 | 
         
             
            βββ app.py                # Main Flask application
         
     | 
| 
         | 
|
| 19 | 
         
             
            βββ templates/
         
     | 
| 20 | 
         
             
            β   βββ index.html        # Main index page with client and host buttons
         
     | 
| 21 | 
         
             
            β   βββ client.html       # Client interface
         
     | 
| 22 | 
         
             
            β   βββ host.html         # Host interface
         
     | 
| 23 | 
         
             
            βββ static/
         
     | 
| 24 | 
         
            -
            β   βββ style.css         #  
     | 
| 25 | 
         
             
            β   βββ script.js         # JavaScript for real-time interactions
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 26 | 
         
             
            βββ requirements.txt      # Python dependencies
         
     | 
| 27 | 
         
             
            βββ README.md             # Project documentation
         
     | 
| 28 | 
         
             
            ```
         
     | 
| 
         @@ -57,12 +62,12 @@ pip install -r requirements.txt 
     | 
|
| 57 | 
         
             
               ```bash
         
     | 
| 58 | 
         
             
               python app.py
         
     | 
| 59 | 
         
             
               ```
         
     | 
| 60 | 
         
            -
            
         
     | 
| 63 | 
         
            -
             
     | 
| 64 | 
         
             
               - **Client interface**: [http://127.0.0.1:5000/client](http://127.0.0.1:5000/client)
         
     | 
| 65 | 
         
            -
            
         
     | 
| 66 | 
         
             
            4. **Access the application**:
         
     | 
| 67 | 
         
             
               - **Host interface**: [http://127.0.0.1:5000/host](http://127.0.0.1:5000/host)
         
     | 
| 68 | 
         
            +
            
         
     | 
| 69 | 
         
             
               - **Client interface**: [http://127.0.0.1:5000/client](http://127.0.0.1:5000/client)
         
     | 
| 70 | 
         
            +
            
         
     | 
| 71 | 
         
             
            ## Application Overview
         
     | 
| 72 | 
         | 
| 73 | 
         
             
            ### Host Interface
         
     | 
    	
        __pycache__/backend.cpython-312.pyc
    ADDED
    
    | 
         Binary file (1.37 kB). View file 
     | 
| 
         | 
    	
        app.py
    CHANGED
    
    | 
         @@ -1,5 +1,6 @@ 
     | 
|
| 1 | 
         
             
            from flask import Flask, render_template, request
         
     | 
| 2 | 
         
             
            from flask_socketio import SocketIO, emit, join_room, leave_room
         
     | 
| 
         | 
|
| 3 | 
         
             
            import matplotlib.pyplot as plt
         
     | 
| 4 | 
         
             
            import base64
         
     | 
| 5 | 
         
             
            from io import BytesIO
         
     | 
| 
         @@ -9,11 +10,8 @@ app = Flask(__name__) 
     | 
|
| 9 | 
         
             
            app.config['SECRET_KEY'] = 'your_secret_key'
         
     | 
| 10 | 
         
             
            socketio = SocketIO(app)
         
     | 
| 11 | 
         | 
| 12 | 
         
            -
             
     | 
| 13 | 
         
            -
             
     | 
| 14 | 
         
            -
                {"question": "What is the largest planet?", "options": ["Earth", "Mars", "Jupiter", "Saturn"], "correct": "Jupiter"}
         
     | 
| 15 | 
         
            -
            ]
         
     | 
| 16 | 
         
            -
            initial_questions = questions.copy()  # Keep a copy of the original questions to reset later
         
     | 
| 17 | 
         
             
            current_question = {"index": 0, "answers": {}, "started": False}
         
     | 
| 18 | 
         
             
            participants = {}
         
     | 
| 19 | 
         | 
| 
         @@ -27,12 +25,12 @@ def client(): 
     | 
|
| 27 | 
         | 
| 28 | 
         
             
            @app.route('/host')
         
     | 
| 29 | 
         
             
            def host():
         
     | 
| 30 | 
         
            -
                return render_template('host.html')
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            @socketio.on('join')
         
     | 
| 33 | 
         
             
            def on_join(data):
         
     | 
| 34 | 
         
             
                username = data['username']
         
     | 
| 35 | 
         
            -
                user_id_number = random.randint(1000, 9999) 
     | 
| 36 | 
         
             
                participants[request.sid] = {"user_id_number": user_id_number, "username": username, "score": 0}
         
     | 
| 37 | 
         
             
                join_room('quiz')
         
     | 
| 38 | 
         
             
                emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
         
     | 
| 
         @@ -47,28 +45,46 @@ def on_leave(): 
     | 
|
| 47 | 
         
             
                    emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
         
     | 
| 48 | 
         
             
                    print(f"{username} left the quiz.")
         
     | 
| 49 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 50 | 
         
             
            @socketio.on('start_quiz')
         
     | 
| 51 | 
         
             
            def start_quiz():
         
     | 
| 52 | 
         
            -
                 
     | 
| 53 | 
         
            -
             
     | 
| 54 | 
         
            -
             
     | 
| 55 | 
         
            -
             
     | 
| 56 | 
         
            -
                     
     | 
| 57 | 
         
            -
                    emit(' 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 58 | 
         | 
| 59 | 
         
             
            @socketio.on('submit_answer')
         
     | 
| 60 | 
         
             
            def receive_answer(data):
         
     | 
| 61 | 
         
             
                username = participants[request.sid]["username"]
         
     | 
| 62 | 
         
             
                answer = data['answer']
         
     | 
| 63 | 
         
             
                current_question['answers'][username] = answer
         
     | 
| 64 | 
         
            -
                 
     | 
| 65 | 
         
            -
                    emit('all_answers_received', room='quiz')
         
     | 
| 66 | 
         | 
| 67 | 
         
             
            @socketio.on('check_answers')
         
     | 
| 68 | 
         
             
            def check_answers():
         
     | 
| 69 | 
         
             
                index = current_question['index']
         
     | 
| 70 | 
         
            -
                if index < len( 
     | 
| 71 | 
         
            -
                    question =  
     | 
| 72 | 
         
             
                    correct_answer = question['correct']
         
     | 
| 73 | 
         
             
                    results = {
         
     | 
| 74 | 
         
             
                        "question": question["question"],
         
     | 
| 
         @@ -76,11 +92,9 @@ def check_answers(): 
     | 
|
| 76 | 
         
             
                        "correct_answer": correct_answer
         
     | 
| 77 | 
         
             
                    }
         
     | 
| 78 | 
         | 
| 79 | 
         
            -
                    # Generate the chart and encode it as base64
         
     | 
| 80 | 
         
             
                    chart_base64 = generate_chart(current_question["answers"], question["options"])
         
     | 
| 81 | 
         
             
                    emit('display_results', {"results": results, "chart": chart_base64}, room='quiz')
         
     | 
| 82 | 
         | 
| 83 | 
         
            -
                    # Update scores based on user_id_number
         
     | 
| 84 | 
         
             
                    for sid, participant in participants.items():
         
     | 
| 85 | 
         
             
                        if current_question['answers'].get(participant["username"]) == correct_answer:
         
     | 
| 86 | 
         
             
                            participants[sid]["score"] += 1
         
     | 
| 
         @@ -89,23 +103,29 @@ def check_answers(): 
     | 
|
| 89 | 
         
             
            def next_question():
         
     | 
| 90 | 
         
             
                current_question['index'] += 1
         
     | 
| 91 | 
         
             
                current_question['answers'] = {}
         
     | 
| 92 | 
         
            -
                if current_question['index'] < len( 
     | 
| 93 | 
         
            -
                    question =  
     | 
| 94 | 
         
            -
                    emit('clear_results', room='quiz') 
     | 
| 95 | 
         
             
                    emit('new_question', question, room='quiz')
         
     | 
| 
         | 
|
| 
         | 
|
| 96 | 
         
             
                else:
         
     | 
| 97 | 
         
             
                    final_results = calculate_final_results()
         
     | 
| 98 | 
         
            -
                    emit(' 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 99 | 
         | 
| 100 | 
         
            -
            @socketio.on('restart_quiz')
         
     | 
| 101 | 
         
            -
            def restart_quiz():
         
     | 
| 102 | 
         
            -
                reset_quiz()
         
     | 
| 103 | 
         
            -
                emit('quiz_reset', room='quiz')
         
     | 
| 104 | 
         | 
| 105 | 
         
             
            def generate_chart(answers, options):
         
     | 
| 
         | 
|
| 106 | 
         
             
                counts = [list(answers.values()).count(option) for option in options]
         
     | 
| 107 | 
         
             
                plt.figure(figsize=(6, 4))
         
     | 
| 108 | 
         
            -
                plt.bar( 
     | 
| 109 | 
         
             
                plt.xlabel('Options')
         
     | 
| 110 | 
         
             
                plt.ylabel('Number of Votes')
         
     | 
| 111 | 
         
             
                plt.title('Results')
         
     | 
| 
         @@ -118,15 +138,14 @@ def generate_chart(answers, options): 
     | 
|
| 118 | 
         
             
                return chart_base64
         
     | 
| 119 | 
         | 
| 120 | 
         
             
            def calculate_final_results():
         
     | 
| 121 | 
         
            -
                 
     | 
| 122 | 
         
            -
                return  
     | 
| 123 | 
         | 
| 124 | 
         
             
            def reset_quiz():
         
     | 
| 125 | 
         
            -
                global  
     | 
| 126 | 
         
            -
                questions = initial_questions.copy()
         
     | 
| 127 | 
         
             
                current_question = {"index": 0, "answers": {}, "started": False}
         
     | 
| 128 | 
         
             
                for participant in participants.values():
         
     | 
| 129 | 
         
             
                    participant["score"] = 0
         
     | 
| 130 | 
         | 
| 131 | 
         
             
            if __name__ == '__main__':
         
     | 
| 132 | 
         
            -
                socketio.run(app, debug=True)
         
     | 
| 
         | 
|
| 1 | 
         
             
            from flask import Flask, render_template, request
         
     | 
| 2 | 
         
             
            from flask_socketio import SocketIO, emit, join_room, leave_room
         
     | 
| 3 | 
         
            +
            import backend  # Import backend functions
         
     | 
| 4 | 
         
             
            import matplotlib.pyplot as plt
         
     | 
| 5 | 
         
             
            import base64
         
     | 
| 6 | 
         
             
            from io import BytesIO
         
     | 
| 
         | 
|
| 10 | 
         
             
            app.config['SECRET_KEY'] = 'your_secret_key'
         
     | 
| 11 | 
         
             
            socketio = SocketIO(app)
         
     | 
| 12 | 
         | 
| 13 | 
         
            +
            exams = backend.load_question_sets()  # Load available exams
         
     | 
| 14 | 
         
            +
            selected_questions = []  # Global variable to store the selected questions
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 15 | 
         
             
            current_question = {"index": 0, "answers": {}, "started": False}
         
     | 
| 16 | 
         
             
            participants = {}
         
     | 
| 17 | 
         | 
| 
         | 
|
| 25 | 
         | 
| 26 | 
         
             
            @app.route('/host')
         
     | 
| 27 | 
         
             
            def host():
         
     | 
| 28 | 
         
            +
                return render_template('host.html', exams=exams)
         
     | 
| 29 | 
         | 
| 30 | 
         
             
            @socketio.on('join')
         
     | 
| 31 | 
         
             
            def on_join(data):
         
     | 
| 32 | 
         
             
                username = data['username']
         
     | 
| 33 | 
         
            +
                user_id_number = random.randint(1000, 9999)
         
     | 
| 34 | 
         
             
                participants[request.sid] = {"user_id_number": user_id_number, "username": username, "score": 0}
         
     | 
| 35 | 
         
             
                join_room('quiz')
         
     | 
| 36 | 
         
             
                emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
         
     | 
| 
         | 
|
| 45 | 
         
             
                    emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
         
     | 
| 46 | 
         
             
                    print(f"{username} left the quiz.")
         
     | 
| 47 | 
         | 
| 48 | 
         
            +
            @socketio.on('load_quiz')
         
     | 
| 49 | 
         
            +
            def load_quiz(data):
         
     | 
| 50 | 
         
            +
                global selected_questions
         
     | 
| 51 | 
         
            +
                exam_name = data['exam_name']
         
     | 
| 52 | 
         
            +
                start_question = data['start_question'] - 1  # Adjust for 0-based indexing
         
     | 
| 53 | 
         
            +
                selected_questions = backend.select_exam(exam_name)
         
     | 
| 54 | 
         
            +
                if selected_questions:
         
     | 
| 55 | 
         
            +
                    num_questions = len(selected_questions)
         
     | 
| 56 | 
         
            +
                    current_question['index'] = start_question
         
     | 
| 57 | 
         
            +
                    emit('quiz_loaded', {"success": True, "num_questions": num_questions, "start_question": start_question + 1}, room=request.sid)
         
     | 
| 58 | 
         
            +
                else:
         
     | 
| 59 | 
         
            +
                    emit('quiz_loaded', {"success": False}, room=request.sid)
         
     | 
| 60 | 
         
            +
             
     | 
| 61 | 
         
             
            @socketio.on('start_quiz')
         
     | 
| 62 | 
         
             
            def start_quiz():
         
     | 
| 63 | 
         
            +
                if participants and selected_questions:
         
     | 
| 64 | 
         
            +
                    current_question['started'] = True
         
     | 
| 65 | 
         
            +
                    emit('new_question', selected_questions[current_question['index']], room='quiz')
         
     | 
| 66 | 
         
            +
                    # Also emit the question to the host 
         
     | 
| 67 | 
         
            +
                    emit('new_question', selected_questions[current_question['index']], room=request.sid)  
         
     | 
| 68 | 
         
            +
                    emit('enable_end_quiz', room=request.sid) # Enable "End Quiz" for the host
         
     | 
| 69 | 
         
            +
             
     | 
| 70 | 
         
            +
            @socketio.on('restart_quiz')
         
     | 
| 71 | 
         
            +
            def restart_quiz():
         
     | 
| 72 | 
         
            +
                reset_quiz()
         
     | 
| 73 | 
         
            +
                emit('quiz_reset', room='quiz')
         
     | 
| 74 | 
         
            +
                start_quiz()
         
     | 
| 75 | 
         | 
| 76 | 
         
             
            @socketio.on('submit_answer')
         
     | 
| 77 | 
         
             
            def receive_answer(data):
         
     | 
| 78 | 
         
             
                username = participants[request.sid]["username"]
         
     | 
| 79 | 
         
             
                answer = data['answer']
         
     | 
| 80 | 
         
             
                current_question['answers'][username] = answer
         
     | 
| 81 | 
         
            +
                print(f"{username} submitted an answer: {answer}")
         
     | 
| 
         | 
|
| 82 | 
         | 
| 83 | 
         
             
            @socketio.on('check_answers')
         
     | 
| 84 | 
         
             
            def check_answers():
         
     | 
| 85 | 
         
             
                index = current_question['index']
         
     | 
| 86 | 
         
            +
                if index < len(selected_questions):
         
     | 
| 87 | 
         
            +
                    question = selected_questions[index]
         
     | 
| 88 | 
         
             
                    correct_answer = question['correct']
         
     | 
| 89 | 
         
             
                    results = {
         
     | 
| 90 | 
         
             
                        "question": question["question"],
         
     | 
| 
         | 
|
| 92 | 
         
             
                        "correct_answer": correct_answer
         
     | 
| 93 | 
         
             
                    }
         
     | 
| 94 | 
         | 
| 
         | 
|
| 95 | 
         
             
                    chart_base64 = generate_chart(current_question["answers"], question["options"])
         
     | 
| 96 | 
         
             
                    emit('display_results', {"results": results, "chart": chart_base64}, room='quiz')
         
     | 
| 97 | 
         | 
| 
         | 
|
| 98 | 
         
             
                    for sid, participant in participants.items():
         
     | 
| 99 | 
         
             
                        if current_question['answers'].get(participant["username"]) == correct_answer:
         
     | 
| 100 | 
         
             
                            participants[sid]["score"] += 1
         
     | 
| 
         | 
|
| 103 | 
         
             
            def next_question():
         
     | 
| 104 | 
         
             
                current_question['index'] += 1
         
     | 
| 105 | 
         
             
                current_question['answers'] = {}
         
     | 
| 106 | 
         
            +
                if current_question['index'] < len(selected_questions):
         
     | 
| 107 | 
         
            +
                    question = selected_questions[current_question['index']]
         
     | 
| 108 | 
         
            +
                    emit('clear_results', room='quiz')
         
     | 
| 109 | 
         
             
                    emit('new_question', question, room='quiz')
         
     | 
| 110 | 
         
            +
                    # Also emit the question to the host
         
     | 
| 111 | 
         
            +
                    emit('new_question', question, room=request.sid)  
         
     | 
| 112 | 
         
             
                else:
         
     | 
| 113 | 
         
             
                    final_results = calculate_final_results()
         
     | 
| 114 | 
         
            +
                    emit('display_final_results', final_results, room='quiz')
         
     | 
| 115 | 
         
            +
             
     | 
| 116 | 
         
            +
            @socketio.on('end_quiz')
         
     | 
| 117 | 
         
            +
            def end_quiz():
         
     | 
| 118 | 
         
            +
                if current_question['started']:  # Ensure the quiz has started before ending it
         
     | 
| 119 | 
         
            +
                    final_results = calculate_final_results()
         
     | 
| 120 | 
         
            +
                    emit('display_final_results', final_results, room='quiz')
         
     | 
| 121 | 
         
            +
                    reset_quiz()  # Reset the quiz state
         
     | 
| 122 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 123 | 
         | 
| 124 | 
         
             
            def generate_chart(answers, options):
         
     | 
| 125 | 
         
            +
                letters = [chr(65 + i) for i in range(len(options))] # Dynamically generate letters for options
         
     | 
| 126 | 
         
             
                counts = [list(answers.values()).count(option) for option in options]
         
     | 
| 127 | 
         
             
                plt.figure(figsize=(6, 4))
         
     | 
| 128 | 
         
            +
                plt.bar(letters, counts)
         
     | 
| 129 | 
         
             
                plt.xlabel('Options')
         
     | 
| 130 | 
         
             
                plt.ylabel('Number of Votes')
         
     | 
| 131 | 
         
             
                plt.title('Results')
         
     | 
| 
         | 
|
| 138 | 
         
             
                return chart_base64
         
     | 
| 139 | 
         | 
| 140 | 
         
             
            def calculate_final_results():
         
     | 
| 141 | 
         
            +
                sorted_scores = sorted(participants.values(), key=lambda x: x['score'], reverse=True)
         
     | 
| 142 | 
         
            +
                return [{"username": p["username"], "score": p["score"]} for p in sorted_scores]
         
     | 
| 143 | 
         | 
| 144 | 
         
             
            def reset_quiz():
         
     | 
| 145 | 
         
            +
                global selected_questions, current_question
         
     | 
| 
         | 
|
| 146 | 
         
             
                current_question = {"index": 0, "answers": {}, "started": False}
         
     | 
| 147 | 
         
             
                for participant in participants.values():
         
     | 
| 148 | 
         
             
                    participant["score"] = 0
         
     | 
| 149 | 
         | 
| 150 | 
         
             
            if __name__ == '__main__':
         
     | 
| 151 | 
         
            +
                socketio.run(app, debug=True)
         
     | 
    	
        assets/2024-10-21-17-07-42.png
    ADDED
    
    
											 
									 | 
									
								
    	
        assets/2024-10-21-17-08-13.png
    ADDED
    
    
											 
									 | 
									
								
    	
        assets/2024-10-21-17-09-15.png
    ADDED
    
    
											 
									 | 
									
								
    	
        backend.py
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            import os
         
     | 
| 2 | 
         
            +
            import json
         
     | 
| 3 | 
         
            +
             
     | 
| 4 | 
         
            +
            # Function to load question sets from the "questions" directory
         
     | 
| 5 | 
         
            +
            def load_question_sets(directory='questions'):
         
     | 
| 6 | 
         
            +
                question_sets = []
         
     | 
| 7 | 
         
            +
                for root, dirs, files in os.walk(directory):
         
     | 
| 8 | 
         
            +
                    for file in files:
         
     | 
| 9 | 
         
            +
                        if file.endswith(".json"):
         
     | 
| 10 | 
         
            +
                            question_sets.append(file[:-5])  # Remove the ".json" extension
         
     | 
| 11 | 
         
            +
                return question_sets
         
     | 
| 12 | 
         
            +
             
     | 
| 13 | 
         
            +
            # Function to select and load the specified exam
         
     | 
| 14 | 
         
            +
            def select_exam(exam_name):
         
     | 
| 15 | 
         
            +
                file_path = os.path.join('questions', f'{exam_name}.json')
         
     | 
| 16 | 
         
            +
                try:
         
     | 
| 17 | 
         
            +
                    with open(file_path, 'r') as f:
         
     | 
| 18 | 
         
            +
                        questions = json.load(f)
         
     | 
| 19 | 
         
            +
                        print(f"Loaded {len(questions)} questions from {exam_name}")
         
     | 
| 20 | 
         
            +
                        return questions
         
     | 
| 21 | 
         
            +
                except FileNotFoundError:
         
     | 
| 22 | 
         
            +
                    print(f"File {file_path} not found.")
         
     | 
| 23 | 
         
            +
                    return []  # Return an empty list if the file is not found
         
     | 
    	
        questions/CLF-C02-v1.json
    ADDED
    
    | 
         The diff for this file is too large to render. 
		See raw diff 
     | 
| 
         | 
    	
        static/script.js
    CHANGED
    
    | 
         @@ -12,21 +12,33 @@ function joinQuiz() { 
     | 
|
| 12 | 
         
             
                document.getElementById('join-title').style.display = 'none';
         
     | 
| 13 | 
         
             
            }
         
     | 
| 14 | 
         | 
| 15 | 
         
            -
             
     | 
| 16 | 
         
            -
                 
     | 
| 17 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 18 | 
         | 
| 19 | 
         
            -
             
     | 
| 20 | 
         
            -
                document.getElementById(' 
     | 
| 21 | 
         
            -
                document.getElementById('question- 
     | 
| 22 | 
         
            -
                 
     | 
| 23 | 
         
            -
             
     | 
| 24 | 
         
            -
             
     | 
| 25 | 
         
            -
             
     | 
| 26 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 27 | 
         | 
| 28 | 
         
            -
            function  
     | 
| 29 | 
         
            -
                 
     | 
| 
         | 
|
| 
         | 
|
| 30 | 
         
             
            }
         
     | 
| 31 | 
         | 
| 32 | 
         
             
            function startQuiz() {
         
     | 
| 
         @@ -41,30 +53,64 @@ function nextQuestion() { 
     | 
|
| 41 | 
         
             
                socket.emit('next_question');
         
     | 
| 42 | 
         
             
            }
         
     | 
| 43 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 44 | 
         
             
            function restartQuiz() {
         
     | 
| 45 | 
         
             
                socket.emit('restart_quiz');
         
     | 
| 46 | 
         
             
            }
         
     | 
| 47 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 48 | 
         
             
            socket.on('display_results', (data) => {
         
     | 
| 49 | 
         
             
                const img = `<img src="data:image/png;base64,${data.chart}" alt="Results Chart" />`;
         
     | 
| 50 | 
         
             
                const resultText = `<p>Correct Answer: ${data.results.correct_answer}</p>`;
         
     | 
| 51 | 
         
             
                document.getElementById('results').innerHTML = img + resultText;
         
     | 
| 52 | 
         
             
            });
         
     | 
| 53 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 54 | 
         
             
            socket.on('clear_results', () => {
         
     | 
| 55 | 
         
             
                document.getElementById('results').innerHTML = '';
         
     | 
| 56 | 
         
             
            });
         
     | 
| 57 | 
         | 
| 58 | 
         
            -
            socket.on(' 
     | 
| 59 | 
         
            -
                 
     | 
| 60 | 
         
            -
                 
     | 
| 61 | 
         
            -
             
     | 
| 62 | 
         
            -
                 
     | 
| 63 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 64 | 
         
             
            });
         
     | 
| 65 | 
         | 
| 66 | 
         
             
            socket.on('quiz_reset', () => {
         
     | 
| 67 | 
         
             
                document.getElementById('results').innerHTML = '';
         
     | 
| 68 | 
         
             
                document.getElementById('question-text').innerText = '';
         
     | 
| 69 | 
         
             
                document.getElementById('options').innerHTML = '';
         
     | 
| 70 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 12 | 
         
             
                document.getElementById('join-title').style.display = 'none';
         
     | 
| 13 | 
         
             
            }
         
     | 
| 14 | 
         | 
| 15 | 
         
            +
            function submitForm(event) {
         
     | 
| 16 | 
         
            +
                event.preventDefault();
         
     | 
| 17 | 
         
            +
                const selectedOption = document.querySelector('input[name="answer"]:checked');
         
     | 
| 18 | 
         
            +
                if (selectedOption) {
         
     | 
| 19 | 
         
            +
                    const answer = selectedOption.value;
         
     | 
| 20 | 
         
            +
                    socket.emit('submit_answer', { answer });
         
     | 
| 21 | 
         
            +
                } else {
         
     | 
| 22 | 
         
            +
                    alert("Please select an option before submitting.");
         
     | 
| 23 | 
         
            +
                }
         
     | 
| 24 | 
         
            +
            }
         
     | 
| 25 | 
         | 
| 26 | 
         
            +
            function selectExam() {
         
     | 
| 27 | 
         
            +
                const examName = document.getElementById('exam-selector').value;
         
     | 
| 28 | 
         
            +
                const startQuestion = document.getElementById('start-question-number').value;
         
     | 
| 29 | 
         
            +
                document.getElementById('question-start-display').textContent = `Starting from question ${startQuestion}.`;
         
     | 
| 30 | 
         
            +
            }
         
     | 
| 31 | 
         
            +
             
     | 
| 32 | 
         
            +
            function loadQuiz() {
         
     | 
| 33 | 
         
            +
                const examName = document.getElementById('exam-selector').value;
         
     | 
| 34 | 
         
            +
                const startQuestion = document.getElementById('start-question-number').value;
         
     | 
| 35 | 
         
            +
                socket.emit('load_quiz', { exam_name: examName, start_question: parseInt(startQuestion) });
         
     | 
| 36 | 
         
            +
            }
         
     | 
| 37 | 
         | 
| 38 | 
         
            +
            function updateSliderValue(value) {
         
     | 
| 39 | 
         
            +
                document.getElementById('start-question').value = value;
         
     | 
| 40 | 
         
            +
                document.getElementById('start-question-number').value = value;
         
     | 
| 41 | 
         
            +
                document.getElementById('question-start-display').textContent = `Starting from question ${value}.`;
         
     | 
| 42 | 
         
             
            }
         
     | 
| 43 | 
         | 
| 44 | 
         
             
            function startQuiz() {
         
     | 
| 
         | 
|
| 53 | 
         
             
                socket.emit('next_question');
         
     | 
| 54 | 
         
             
            }
         
     | 
| 55 | 
         | 
| 56 | 
         
            +
            function endQuiz() {
         
     | 
| 57 | 
         
            +
                socket.emit('end_quiz');
         
     | 
| 58 | 
         
            +
            }
         
     | 
| 59 | 
         
            +
             
     | 
| 60 | 
         
             
            function restartQuiz() {
         
     | 
| 61 | 
         
             
                socket.emit('restart_quiz');
         
     | 
| 62 | 
         
             
            }
         
     | 
| 63 | 
         | 
| 64 | 
         
            +
            socket.on('quiz_loaded', (data) => {
         
     | 
| 65 | 
         
            +
                if (data.success) {
         
     | 
| 66 | 
         
            +
                    alert(`Quiz loaded with ${data.num_questions} questions, starting from question ${data.start_question}.`);
         
     | 
| 67 | 
         
            +
                } else {
         
     | 
| 68 | 
         
            +
                    alert(`Failed to load quiz.`);
         
     | 
| 69 | 
         
            +
                }
         
     | 
| 70 | 
         
            +
            });
         
     | 
| 71 | 
         
            +
             
     | 
| 72 | 
         
            +
            socket.on('new_question', (data) => {
         
     | 
| 73 | 
         
            +
                document.getElementById('waiting-message').style.display = 'none';
         
     | 
| 74 | 
         
            +
                document.getElementById('question-text').innerText = data.question;
         
     | 
| 75 | 
         
            +
                // Dynamically generate letters for options (up to 'h')
         
     | 
| 76 | 
         
            +
                const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; 
         
     | 
| 77 | 
         
            +
                const options = data.options.map((opt, index) =>
         
     | 
| 78 | 
         
            +
                    `<input type="radio" id="${letters[index]}" name="answer" value="${opt}">
         
     | 
| 79 | 
         
            +
                     <label for="${letters[index]}">${letters[index]}) ${opt}</label><br>`
         
     | 
| 80 | 
         
            +
                ).join('');
         
     | 
| 81 | 
         
            +
                document.getElementById('options').innerHTML = options;
         
     | 
| 82 | 
         
            +
            });
         
     | 
| 83 | 
         
            +
             
     | 
| 84 | 
         
             
            socket.on('display_results', (data) => {
         
     | 
| 85 | 
         
             
                const img = `<img src="data:image/png;base64,${data.chart}" alt="Results Chart" />`;
         
     | 
| 86 | 
         
             
                const resultText = `<p>Correct Answer: ${data.results.correct_answer}</p>`;
         
     | 
| 87 | 
         
             
                document.getElementById('results').innerHTML = img + resultText;
         
     | 
| 88 | 
         
             
            });
         
     | 
| 89 | 
         | 
| 90 | 
         
            +
            socket.on('enable_end_quiz', () => {
         
     | 
| 91 | 
         
            +
                document.getElementById('end-quiz').disabled = false; // Enable the "End Quiz" button
         
     | 
| 92 | 
         
            +
            });
         
     | 
| 93 | 
         
            +
             
     | 
| 94 | 
         
             
            socket.on('clear_results', () => {
         
     | 
| 95 | 
         
             
                document.getElementById('results').innerHTML = '';
         
     | 
| 96 | 
         
             
            });
         
     | 
| 97 | 
         | 
| 98 | 
         
            +
            socket.on('display_final_results', (finalResults) => {
         
     | 
| 99 | 
         
            +
                document.getElementById('quiz-content').style.display = 'none';
         
     | 
| 100 | 
         
            +
                const resultsTable = document.getElementById('results-table');
         
     | 
| 101 | 
         
            +
                resultsTable.innerHTML = '';
         
     | 
| 102 | 
         
            +
                finalResults.forEach((participant) => {
         
     | 
| 103 | 
         
            +
                    const row = `<tr><td>${participant.username}</td><td>${participant.score}</td></tr>`;
         
     | 
| 104 | 
         
            +
                    resultsTable.innerHTML += row;
         
     | 
| 105 | 
         
            +
                });
         
     | 
| 106 | 
         
            +
                document.getElementById('final-results').style.display = 'block';
         
     | 
| 107 | 
         
             
            });
         
     | 
| 108 | 
         | 
| 109 | 
         
             
            socket.on('quiz_reset', () => {
         
     | 
| 110 | 
         
             
                document.getElementById('results').innerHTML = '';
         
     | 
| 111 | 
         
             
                document.getElementById('question-text').innerText = '';
         
     | 
| 112 | 
         
             
                document.getElementById('options').innerHTML = '';
         
     | 
| 113 | 
         
            +
                document.getElementById('final-results').style.display = 'none';
         
     | 
| 114 | 
         
            +
                document.getElementById('quiz-content').style.display = 'block';
         
     | 
| 115 | 
         
            +
                document.getElementById('waiting-message').style.display = 'block';
         
     | 
| 116 | 
         
            +
            });
         
     | 
    	
        static/style.css
    ADDED
    
    | 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            body {
         
     | 
| 2 | 
         
            +
                background-color: #f4f4f4;
         
     | 
| 3 | 
         
            +
                font-family: Arial, sans-serif;
         
     | 
| 4 | 
         
            +
            }
         
     | 
| 5 | 
         
            +
             
     | 
| 6 | 
         
            +
            .container {
         
     | 
| 7 | 
         
            +
                max-width: 800px;
         
     | 
| 8 | 
         
            +
                margin: 0 auto;
         
     | 
| 9 | 
         
            +
                background-color: #fff;
         
     | 
| 10 | 
         
            +
                padding: 20px;
         
     | 
| 11 | 
         
            +
                border-radius: 8px;
         
     | 
| 12 | 
         
            +
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
         
     | 
| 13 | 
         
            +
            }
         
     | 
| 14 | 
         
            +
             
     | 
| 15 | 
         
            +
            h2, p {
         
     | 
| 16 | 
         
            +
                font-size: 18px;
         
     | 
| 17 | 
         
            +
            }
         
     | 
| 18 | 
         
            +
             
     | 
| 19 | 
         
            +
            input[type="radio"] {
         
     | 
| 20 | 
         
            +
                margin-right: 10px;
         
     | 
| 21 | 
         
            +
            }
         
     | 
| 22 | 
         
            +
             
     | 
| 23 | 
         
            +
            input[type="submit"] {
         
     | 
| 24 | 
         
            +
                display: block;
         
     | 
| 25 | 
         
            +
                margin-top: 20px;
         
     | 
| 26 | 
         
            +
            }
         
     | 
    	
        templates/client.html
    CHANGED
    
    | 
         @@ -5,6 +5,7 @@ 
     | 
|
| 5 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         
     | 
| 6 | 
         
             
                <title>Quiz Client</title>
         
     | 
| 7 | 
         
             
                <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
         
     | 
| 
         | 
|
| 8 | 
         
             
            </head>
         
     | 
| 9 | 
         
             
            <body>
         
     | 
| 10 | 
         
             
                <div class="container">
         
     | 
| 
         @@ -14,10 +15,28 @@ 
     | 
|
| 14 | 
         
             
                    <div id="quiz-content" style="display: none;">
         
     | 
| 15 | 
         
             
                        <h3>Logged in as: <span id="logged-user"></span></h3>
         
     | 
| 16 | 
         
             
                        <h3 id="waiting-message" style="display: none;">Waiting for the Host...</h3>
         
     | 
| 17 | 
         
            -
                        < 
     | 
| 18 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 19 | 
         
             
                        <div id="results" class="mt-4"></div>
         
     | 
| 20 | 
         
             
                    </div>
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 21 | 
         
             
                </div>
         
     | 
| 22 | 
         
             
                <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
         
     | 
| 23 | 
         
             
                <script src="/static/script.js"></script>
         
     | 
| 
         | 
|
| 5 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         
     | 
| 6 | 
         
             
                <title>Quiz Client</title>
         
     | 
| 7 | 
         
             
                <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
         
     | 
| 8 | 
         
            +
                <link rel="stylesheet" href="/static/style.css">
         
     | 
| 9 | 
         
             
            </head>
         
     | 
| 10 | 
         
             
            <body>
         
     | 
| 11 | 
         
             
                <div class="container">
         
     | 
| 
         | 
|
| 15 | 
         
             
                    <div id="quiz-content" style="display: none;">
         
     | 
| 16 | 
         
             
                        <h3>Logged in as: <span id="logged-user"></span></h3>
         
     | 
| 17 | 
         
             
                        <h3 id="waiting-message" style="display: none;">Waiting for the Host...</h3>
         
     | 
| 18 | 
         
            +
                        <div id="question-section" class="mt-4">
         
     | 
| 19 | 
         
            +
                            <form id="quiz-form" onsubmit="submitForm(event)">
         
     | 
| 20 | 
         
            +
                                <p id="question-text"></p>
         
     | 
| 21 | 
         
            +
                                <div id="options"></div>
         
     | 
| 22 | 
         
            +
                                <input type="submit" value="Submit" class="btn btn-primary mt-2">
         
     | 
| 23 | 
         
            +
                            </form>
         
     | 
| 24 | 
         
            +
                        </div>
         
     | 
| 25 | 
         
             
                        <div id="results" class="mt-4"></div>
         
     | 
| 26 | 
         
             
                    </div>
         
     | 
| 27 | 
         
            +
                    <div id="final-results" style="display: none;" class="mt-4">
         
     | 
| 28 | 
         
            +
                        <h3>And the Winners are:</h3>
         
     | 
| 29 | 
         
            +
                        <table class="table mt-2">
         
     | 
| 30 | 
         
            +
                            <thead>
         
     | 
| 31 | 
         
            +
                                <tr>
         
     | 
| 32 | 
         
            +
                                    <th>Participant</th>
         
     | 
| 33 | 
         
            +
                                    <th>Score</th>
         
     | 
| 34 | 
         
            +
                                </tr>
         
     | 
| 35 | 
         
            +
                            </thead>
         
     | 
| 36 | 
         
            +
                            <tbody id="results-table">
         
     | 
| 37 | 
         
            +
                            </tbody>
         
     | 
| 38 | 
         
            +
                        </table>
         
     | 
| 39 | 
         
            +
                    </div>
         
     | 
| 40 | 
         
             
                </div>
         
     | 
| 41 | 
         
             
                <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
         
     | 
| 42 | 
         
             
                <script src="/static/script.js"></script>
         
     | 
    	
        templates/host.html
    CHANGED
    
    | 
         @@ -5,17 +5,32 @@ 
     | 
|
| 5 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         
     | 
| 6 | 
         
             
                <title>Quiz Host</title>
         
     | 
| 7 | 
         
             
                <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
         
     | 
| 
         | 
|
| 8 | 
         
             
            </head>
         
     | 
| 9 | 
         
             
            <body>
         
     | 
| 10 | 
         
             
                <div class="container">
         
     | 
| 11 | 
         
             
                    <h2>Quiz Host</h2>
         
     | 
| 12 | 
         
             
                    <p>Participants connected: <span id="participant-count">0</span></p>
         
     | 
| 13 | 
         
            -
                    < 
     | 
| 14 | 
         
            -
             
     | 
| 15 | 
         
            -
             
     | 
| 16 | 
         
            -
             
     | 
| 17 | 
         
            -
             
     | 
| 18 | 
         
            -
                     
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 19 | 
         
             
                    <div id="results" class="mt-4"></div>
         
     | 
| 20 | 
         
             
                </div>
         
     | 
| 21 | 
         
             
                <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
         
     | 
| 
         | 
|
| 5 | 
         
             
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         
     | 
| 6 | 
         
             
                <title>Quiz Host</title>
         
     | 
| 7 | 
         
             
                <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
         
     | 
| 8 | 
         
            +
                <link rel="stylesheet" href="/static/style.css">
         
     | 
| 9 | 
         
             
            </head>
         
     | 
| 10 | 
         
             
            <body>
         
     | 
| 11 | 
         
             
                <div class="container">
         
     | 
| 12 | 
         
             
                    <h2>Quiz Host</h2>
         
     | 
| 13 | 
         
             
                    <p>Participants connected: <span id="participant-count">0</span></p>
         
     | 
| 14 | 
         
            +
                    <select id="exam-selector" class="form-control" onchange="selectExam()">
         
     | 
| 15 | 
         
            +
                        <option value="" disabled selected>Select an exam</option>
         
     | 
| 16 | 
         
            +
                        {% for exam in exams %}
         
     | 
| 17 | 
         
            +
                            <option value="{{ exam }}">{{ exam }}</option>
         
     | 
| 18 | 
         
            +
                        {% endfor %}
         
     | 
| 19 | 
         
            +
                    </select>
         
     | 
| 20 | 
         
            +
                    <label for="start-question" class="mt-3">Select starting question:</label>
         
     | 
| 21 | 
         
            +
                    <input type="range" id="start-question" min="1" max="10" value="1" class="form-range mt-2 mb-2" oninput="updateSliderValue(this.value)">
         
     | 
| 22 | 
         
            +
                    <input type="number" id="start-question-number" min="1" max="10" value="1" class="form-control" oninput="updateSliderValue(this.value)">
         
     | 
| 23 | 
         
            +
                    <p id="question-start-display" class="mt-2">Starting from question 1.</p>
         
     | 
| 24 | 
         
            +
                    <button onclick="loadQuiz()" class="btn btn-info mt-3">Load Quiz</button><br><br>
         
     | 
| 25 | 
         
            +
                    <button onclick="startQuiz()" class="btn btn-success mt-3">Start Quiz</button><br><br>
         
     | 
| 26 | 
         
            +
                    <button onclick="restartQuiz()" class="btn btn-warning mt-3">Restart</button><br><br>
         
     | 
| 27 | 
         
            +
                    <button onclick="checkAnswers()" class="btn btn-primary mt-2">Check Answers</button><br><br>
         
     | 
| 28 | 
         
            +
                    <button onclick="nextQuestion()" class="btn btn-secondary mt-2">Next Question</button><br><br>
         
     | 
| 29 | 
         
            +
                    <button onclick="endQuiz()" id="end-quiz" class="btn btn-danger mt-2" disabled>End Quiz</button>
         
     | 
| 30 | 
         
            +
                    <div id="question-section" class="mt-4">
         
     | 
| 31 | 
         
            +
                        <p id="question-text"></p>
         
     | 
| 32 | 
         
            +
                        <div id="options"></div>
         
     | 
| 33 | 
         
            +
                    </div>
         
     | 
| 34 | 
         
             
                    <div id="results" class="mt-4"></div>
         
     | 
| 35 | 
         
             
                </div>
         
     | 
| 36 | 
         
             
                <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
         
     |