diff --git a/app/controllers/socket_conroller.py b/app/controllers/socket_conroller.py index 41ba622..4de99d0 100644 --- a/app/controllers/socket_conroller.py +++ b/app/controllers/socket_conroller.py @@ -38,34 +38,36 @@ class SocketController: self.admin_sids: dict[str, str] = {} self._register_events() - # --------------------------------------------------------------------- - # Helper utilities - # --------------------------------------------------------------------- @staticmethod def _is_correct(user_answer, question: dict) -> bool: - """Bandingkan jawaban user dengan kunci jawaban.""" + """Advanced answer validation method.""" + print(f"Validating answer: user={user_answer}, question={question}") - print("user answer", user_answer, "question_target", question["target_answer"]) - if question["type"] == "fill_the_blank": - return ( - str(user_answer).strip().lower() - == str(question["target_answer"]).strip().lower() - ) - elif question["type"] == "true_false": - return bool(user_answer) == question["target_answer"] - elif question["type"] == "option": - # user_answer bisa dikirim sebagai index atau value teks option. - # Pastikan diโ€‘cast ke int terlebih dahulu jika memungkinkan. - try: - return int(user_answer) == question["target_answer"] - except ValueError: + try: + if question["type"] == "fill_the_blank": return ( str(user_answer).strip().lower() - == str(question["options"][question["target_answer"]]) - .strip() - .lower() + == str(question["target_answer"]).strip().lower() ) - return False + elif question["type"] == "true_false": + return bool(user_answer) == question["target_answer"] + elif question["type"] == "option": + # Handle both index and text-based answers + try: + # First try numeric index comparison + return int(user_answer) == question["target_answer"] + except ValueError: + # If not an index, compare text + return ( + str(user_answer).strip().lower() + == str(question["options"][question["target_answer"]]) + .strip() + .lower() + ) + return False + except Exception as e: + print(f"Error in answer validation: {e}") + return False def _questions_key(self, session_code: str) -> str: return f"session:{session_code}:questions" @@ -239,7 +241,6 @@ class SocketController: def handle_end_session(data): session_code = data.get("session_id") user_id = data.get("user_id") - if not session_code or not user_id: emit("error", {"message": "session_id and user_id required"}) return @@ -247,21 +248,6 @@ class SocketController: # Validasi user berhak mengakhiri session self.session_service.end_session(session_id=session_code, user_id=user_id) - answers = self.redis_repo.get_data(self._answers_key(session_code)) or [] - scores = self.redis_repo.get_data(self._scores_key(session_code)) or {} - - print("\n๐Ÿ“ฆ Final Quiz Data for Session", session_code) - print("------------------------------------------------------------") - for entry in answers: - status = "โœ”" if entry["correct"] else "โœ–" - print( - f"User {entry['user_id']} - Q{entry['question_index']}: {entry['answer']} {status}" - ) - - print("\n๐Ÿ Rekap Skor:") - for uid, sc in scores.items(): - print(f"User {uid}: Benar {sc['correct']} | Salah {sc['incorrect']}") - # Bersihkan semua data session di Redis for key in [ self._answers_key(session_code), @@ -288,11 +274,8 @@ class SocketController: target=self._simulate_quiz_flow, args=(session_code,), daemon=True ).start() - # --------------------------------------------------------------------- - # Quiz flow simulation ------------------------------------------------- - # --------------------------------------------------------------------- def _simulate_quiz_flow(self, session_code: str): - """Mengirim list pertanyaan satu per satu secara otomatis (demo).""" + """Enhanced quiz flow with better question management.""" questions = [ { "index": 1, @@ -300,6 +283,7 @@ class SocketController: "target_answer": "Kutai", "duration": 30, "type": "fill_the_blank", + "points": 10, "options": None, }, { @@ -308,6 +292,7 @@ class SocketController: "target_answer": True, "duration": 30, "type": "true_false", + "points": 10, "options": None, }, { @@ -316,88 +301,138 @@ class SocketController: "target_answer": 2, "duration": 30, "type": "option", + "points": 10, "options": ["Majapahit", "Tarumanegara", "Sriwijaya", "Mataram Kuno"], }, - { - "index": 4, - "question": "Prasasti Yupa merupakan peninggalan dari kerajaan?", - "target_answer": "Kutai", - "duration": 30, - "type": "fill_the_blank", - "options": None, - }, - { - "index": 5, - "question": "Apakah Tarumanegara terletak di wilayah Kalimantan Timur?", - "target_answer": False, - "duration": 30, - "type": "true_false", - "options": None, - }, - { - "index": 6, - "question": "Kitab Negarakertagama ditulis oleh?", - "target_answer": 0, - "duration": 30, - "type": "option", - "options": [ - "Empu Tantular", - "Empu Prapanca", - "Hayam Wuruk", - "Gajah Mada", - ], - }, - { - "index": 7, - "question": "Tokoh yang terkenal dengan Sumpah Palapa adalah?", - "target_answer": "Gajah Mada", - "duration": 30, - "type": "fill_the_blank", - "options": None, - }, - { - "index": 8, - "question": "Candi Borobudur dibangun oleh kerajaan Hindu?", - "target_answer": False, - "duration": 30, - "type": "true_false", - "options": None, - }, - { - "index": 9, - "question": "Raja terkenal dari Kerajaan Sriwijaya adalah?", - "target_answer": 1, - "duration": 30, - "type": "option", - "options": [ - "Dapunta Hyang", - "Balaputradewa", - "Airlangga", - "Hayam Wuruk", - ], - }, - { - "index": 10, - "question": "Candi Prambanan merupakan peninggalan agama?", - "target_answer": "Hindu", - "duration": 30, - "type": "fill_the_blank", - "options": None, - }, ] - # Simpan ke Redis agar bisa dipakai saat evaluasi jawaban + # Simpan ke Redis self.redis_repo.set_data(self._questions_key(session_code), questions) - # Beri sedikit jeda sebelum pertanyaan pertama + # Inisialisasi skor untuk semua peserta + scores = self.redis_repo.get_data(self._scores_key(session_code)) or {} + self.redis_repo.set_data(self._scores_key(session_code), scores) + + # Beri jeda sebelum pertanyaan pertama time.sleep(2) for q in questions: print(f"\n๐Ÿ“ข Mengirim pertanyaan {q['index']} ke room {session_code}") - q.pop("target_answer") - self.socketio.emit("quiz_question", q, room=session_code) + + # Kirim pertanyaan tanpa jawaban yang benar + question_to_send = q.copy() + question_to_send.pop("target_answer", None) + + self.socketio.emit("quiz_question", question_to_send, room=session_code) + + # Tunggu durasi pertanyaan time.sleep(q["duration"]) - self.socketio.emit( - "quiz_done", {"message": "Quiz has ended!"}, room=session_code - ) + # Generate dan kirim rekap kuis + self._generate_quiz_recap(session_code) + + def _generate_quiz_recap(self, session_code: str): + """Comprehensive quiz recap generation.""" + try: + # Ambil data dari Redis + answers = self.redis_repo.get_data(self._answers_key(session_code)) or [] + scores = self.redis_repo.get_data(self._scores_key(session_code)) or {} + questions = ( + self.redis_repo.get_data(self._questions_key(session_code)) or [] + ) + + # Persiapkan data rekap + recap_data = { + "session_code": session_code, + "total_questions": len(questions), + "answers": [], + "scores": [], + "questions": [], + } + + # Tambahkan detail pertanyaan + for q in questions: + question_recap = { + "index": q["index"], + "question": q["question"], + "type": q["type"], + "points": q.get("points", 10), + } + recap_data["questions"].append(question_recap) + + # Tambahkan detail jawaban + for entry in answers: + # Temukan pertanyaan terkait + related_question = next( + (q for q in questions if q["index"] == entry["question_index"]), + None, + ) + + answer_recap = { + "user_id": entry["user_id"], + "question_index": entry["question_index"], + "answer": entry["answer"], + "correct": entry["correct"], + "question": ( + related_question["question"] + if related_question + else "Pertanyaan tidak ditemukan" + ), + } + recap_data["answers"].append(answer_recap) + + # Tambahkan skor per pengguna + for uid, sc in scores.items(): + score_recap = { + "user_id": uid, + "correct": sc.get("correct", 0), + "incorrect": sc.get("incorrect", 0), + "total_score": sc.get("correct", 0) + * 10, # 10 poin per jawaban benar + } + recap_data["scores"].append(score_recap) + + # Urutkan skor dari tertinggi ke terendah + recap_data["scores"].sort(key=lambda x: x["total_score"], reverse=True) + + # Kirim rekap ke semua peserta + self.socketio.emit( + "quiz_recap", + { + "message": "Kuis telah selesai. Berikut adalah rekap lengkap.", + "recap": recap_data, + }, + room=session_code, + ) + + print(recap_data) + + # Cetak rekap di konsol server + print("\n๐Ÿ Rekap Kuis Lengkap") + print("=" * 50) + print(f"Kode Sesi: {session_code}") + print(f"Total Pertanyaan: {len(questions)}") + print("\nPeringkat Peserta:") + for i, score in enumerate(recap_data["scores"], 1): + print( + f"{i}. User {score['user_id']}: Skor {score['total_score']} (Benar: {score['correct']}, Salah: {score['incorrect']})" + ) + + # Kirim tanda kuis telah berakhir + self.socketio.emit( + "quiz_done", + {"message": "Kuis telah berakhir.", "session_code": session_code}, + room=session_code, + ) + + except Exception as e: + print(f"Error generating quiz recap: {e}") + # Kirim pesan error jika gagal membuat rekap + self.socketio.emit( + "quiz_error", + { + "message": "Terjadi kesalahan saat membuat rekap kuis.", + "error": str(e), + }, + room=session_code, + )