import json import asyncio from channels.generic.websocket import AsyncWebsocketConsumer from channels.db import database_sync_to_async from django.utils import timezone from django.urls import reverse from play.models import QuizGame, QuizGameParticipant, QuizAnswer from django.conf import settings class GameConsumer(AsyncWebsocketConsumer): async def connect(self): self.join_code = self.scope['url_route']['kwargs']['join_code'] self.game_group_name = f'game_{self.join_code}' # Join game group await self.channel_layer.group_add( self.game_group_name, self.channel_name ) await self.accept() async def disconnect(self, close_code): # Leave game group await self.channel_layer.group_discard( self.game_group_name, self.channel_name ) # Check if game should be deleted await self.cleanup_game() async def receive(self, text_data): text_data_json = json.loads(text_data) message_type = text_data_json['type'] if message_type == 'ping': # Respond with pong to keep connection alive await self.send(text_data=json.dumps({ 'type': 'pong' })) return if message_type == 'leave_game': participant_id = text_data_json.get('participant_id') if participant_id: try: # Get participant info before deletion participant = await database_sync_to_async(QuizGameParticipant.objects.get)(participant_id=participant_id) display_name = participant.display_name join_code = self.join_code # Delete participant await database_sync_to_async(participant.delete)() # Update participants list participants = await self.get_participants() await self.channel_layer.group_send( self.game_group_name, { 'type': 'participant_list_update', 'participants': participants } ) # Send redirect to home await self.send(text_data=json.dumps({ 'type': 'redirect', 'url': '/' })) except QuizGameParticipant.DoesNotExist: pass return if message_type == 'submit_rating': participant_id = text_data_json['participant_id'] rating = text_data_json['rating'] success = await self.save_rating(participant_id, rating) if success: await self.send(text_data=json.dumps({ 'type': 'rating_confirmed' })) elif message_type == 'submit_answer': participant_id = text_data_json['participant_id'] try: answer = text_data_json['answer'] except: answer=-1 time_remaining = text_data_json.get('time_remaining', 0) # Save answer and update participant score await self.save_answer(participant_id, answer, time_remaining) # Notify host about the new answer await self.channel_layer.group_send( self.game_group_name, { 'type': 'participant_answer', 'answer': answer } ) elif message_type == 'start_game': host_id = text_data_json['host_id'] if await self.verify_host(host_id): await self.advance_to_next_question() elif message_type == 'get_answer_stats': stats = await self.get_answer_stats() await self.send(text_data=json.dumps({ 'type': 'answer_stats', 'stats': stats })) elif message_type == 'update_participants': participants = await self.get_participants() await self.channel_layer.group_send( self.game_group_name, { 'type': 'participant_list_update', 'participants': participants } ) elif message_type == 'next_question': host_id = text_data_json['host_id'] if await self.verify_host(host_id): await self.advance_to_next_question() elif message_type == 'finish_game': host_id = text_data_json['host_id'] if await self.verify_host(host_id): await self.finish_game() elif message_type == 'advance_to_scores': host_id = text_data_json['host_id'] if await self.verify_host(host_id): await self.show_scores() async def participant_answer(self, event): await self.send(text_data=json.dumps({ 'type': 'participant_answer', 'answer': event['answer'] })) async def participant_list_update(self, event): await self.send(text_data=json.dumps({ 'type': 'participant_list_update', 'participants': event['participants'] })) async def player_left(self, event): await self.send(text_data=json.dumps({ 'type': 'player_left', 'player_name': event['player_name'], 'was_kicked': event.get('was_kicked', False) })) async def game_state_update(self, event): await self.send(text_data=json.dumps({ 'type': 'game_state_update', 'action': event['action'], 'redirect_url': event.get('redirect_url') })) @database_sync_to_async def verify_host(self, host_id): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) return quiz_game.host_id == host_id except QuizGame.DoesNotExist: return False @database_sync_to_async def save_answer(self, participant_id, answer_index, time_remaining): try: participant = QuizGameParticipant.objects.get( quiz_game__join_code=self.join_code, participant_id=participant_id ) quiz_game = participant.quiz_game current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index] # Check if answer is correct is_correct = False question_data = json.loads(current_question.data) print(answer_index) try: if answer_index >= 0 : # -1 means timeout is_correct = question_data['options'][answer_index]['is_correct'] print(is_correct) except: user_input = str(answer_index).strip().lower().replace(" ", "") correct_value = str(question_data['options'][0].get('value', '')).strip().lower().replace(" ", "") print("Richtig",correct_value) print(user_input) if user_input == correct_value: is_correct=True answer_index=int(0) print(is_correct) else: answer_index=int(1) # Calculate score based on correctness and time score = 0 if is_correct: base_score = 1000 time_factor = time_remaining / int(current_question.time_per_question)/int(1000) score = int(base_score * (0.5 + 0.5 * time_factor)) # Update or create answer in QuizAnswer table answer_obj, created = QuizAnswer.objects.get_or_create( participant=participant, question_index=quiz_game.current_question_index, defaults={ 'answer_index': answer_index, 'is_correct': is_correct, 'score': score, 'time_remaining': time_remaining } ) if not created: answer_obj.answer_index = answer_index answer_obj.is_correct = is_correct answer_obj.score = score answer_obj.time_remaining = time_remaining answer_obj.save() participant.last_score = score participant.score += score participant.last_answer_correct = is_correct participant.save() return True except (QuizGameParticipant.DoesNotExist, IndexError): return False @database_sync_to_async def get_answer_stats(self): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) # Count answers for each option in the current question stats = {} answers = QuizAnswer.objects.filter( participant__quiz_game=quiz_game, question_index=quiz_game.current_question_index ) for answer in answers: if answer.answer_index >= 0: # Ignore timeouts (-1) stats[answer.answer_index] = stats.get(answer.answer_index, 0) + 1 return stats except QuizGame.DoesNotExist: return {} @database_sync_to_async def save_rating(self, participant_id, rating): from django.db import models if not settings.QIVIP_CONFIG['ENABLE_RATING_SYSTEM']: return False from library.models import QuizRating try: game = QuizGame.objects.get(join_code=self.join_code) participant = QuizGameParticipant.objects.get( quiz_game=game, participant_id=participant_id ) # Update or create rating """ rating_obj, created = QuizRating.objects.get_or_create( quiz=game.quiz_id, participant_id=participant_id, defaults={'rating': rating} ) """ rating_obj=QuizRating.objects.create( quiz=game.quiz_id, participant_id=participant_id, rating=rating) rating_obj.rating = rating rating_obj.save() return True except (QuizGame.DoesNotExist, QuizGameParticipant.DoesNotExist): return False @database_sync_to_async def cleanup_game(self): try: game = QuizGame.objects.get(join_code=self.join_code) active_participants = QuizGameParticipant.objects.filter( quiz_game=game ).count() if active_participants == 0: game.delete() except QuizGame.DoesNotExist: pass @database_sync_to_async def get_participants(self): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game) return [ { 'id': p.participant_id, 'display_name': p.display_name, 'score': p.score } for p in participants ] except QuizGame.DoesNotExist: return [] @database_sync_to_async def _advance_to_next_question(self): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) quiz = quiz_game.quiz_id # Move to next question quiz_game.current_question_index += 1 # Check if we've reached the end if quiz_game.current_question_index >= quiz.questions.count(): quiz_game.current_state = 'finished' quiz_game.save() return 'finished' else: # Update game state and start time quiz_game.current_state = 'question' quiz_game.question_start_time = timezone.now() quiz_game.save() return 'question' except QuizGame.DoesNotExist: return None async def advance_to_next_question(self): result = await self._advance_to_next_question() if result == 'finished': # Notify clients to redirect to finished page await self.channel_layer.group_send( self.game_group_name, { 'type': 'game_state_update', 'action': 'finish_game', 'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code}) } ) elif result == 'question': # Notify clients to redirect to next question await self.channel_layer.group_send( self.game_group_name, { 'type': 'game_state_update', 'action': 'next_question', 'redirect_url': reverse('play:question', kwargs={'join_code': self.join_code}) } ) @database_sync_to_async def _show_scores(self): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) quiz_game.current_state = 'scores' quiz_game.save() return True except QuizGame.DoesNotExist: return False async def show_scores(self): success = await self._show_scores() if success: # Notify clients to redirect to scores page await self.channel_layer.group_send( self.game_group_name, { 'type': 'game_state_update', 'action': 'show_scores', 'redirect_url': reverse('play:scores', kwargs={'join_code': self.join_code}) } ) @database_sync_to_async def _finish_game(self): try: quiz_game = QuizGame.objects.get(join_code=self.join_code) quiz_game.current_state = 'finished' quiz_game.save() return True except QuizGame.DoesNotExist: return False async def finish_game(self): success = await self._finish_game() if success: # Notify clients to redirect to finished page await self.channel_layer.group_send( self.game_group_name, { 'type': 'game_state_update', 'action': 'finish_game', 'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code}) } )