First Iteration of Map Question Type

This commit is contained in:
juhnsa
2025-07-15 23:26:18 +02:00
committed by juhn
parent 760ba5be06
commit ded3328d06
10 changed files with 660 additions and 65 deletions

View File

@@ -6,6 +6,7 @@ from django.utils import timezone
from django.urls import reverse
from play.models import QuizGame, QuizGameParticipant, QuizAnswer
from django.conf import settings
from geopy.distance import geodesic
class GameConsumer(AsyncWebsocketConsumer):
@@ -31,6 +32,13 @@ class GameConsumer(AsyncWebsocketConsumer):
# Check if game should be deleted
await self.cleanup_game()
@database_sync_to_async
def get_question_data(self):
quiz_game = QuizGame.objects.get(join_code=self.join_code)
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
return json.loads(current_question.data)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message_type = text_data_json['type']
@@ -84,14 +92,16 @@ class GameConsumer(AsyncWebsocketConsumer):
elif message_type == 'submit_answer':
participant_id = text_data_json['participant_id']
try:
answer = text_data_json['answer']
except:
answer=-1
answer = text_data_json['answer']
time_remaining = text_data_json.get('time_remaining', 0)
# Save answer and update participant score
await self.save_answer(participant_id, answer, time_remaining)
question_data = await self.get_question_data()
if question_data['type'] == "map":
await self.save_answer_map(participant_id, answer, time_remaining)
else:
await self.save_answer(participant_id, answer, time_remaining)
# Notify host about the new answer
await self.channel_layer.group_send(
@@ -182,6 +192,97 @@ class GameConsumer(AsyncWebsocketConsumer):
except QuizGame.DoesNotExist:
return False
@database_sync_to_async
def save_answer_map(self, participant_id, answer_location, time_remaining):
answer_index = -1
try:
# Get all necessary data first
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]
question_data = json.loads(current_question.data)
target_location = question_data['target_location']
tolerance_radius = question_data['tolerance_radius']
print(f"target_location: {target_location}")
print(f"answer_location: {answer_location}")
print(f"tolerance_radius: {tolerance_radius}")
# Configuration - Make these easily adjustable
MAX_DISTANCE_KM = 1000 # Maximum distance for scoring (in km)
DIRECT_HIT_BONUS = 200 # Extra points for direct hit within tolerance
# Calculate base score
distance_km = geodesic(answer_location, target_location).kilometers
tolerance_km = int(tolerance_radius) / 1000 # Convert meters to kilometers
# Calculate base score with non-linear decrease
if distance_km <= tolerance_km:
# Direct hit - full points + bonus
base_score = 1000 + DIRECT_HIT_BONUS # 1000 + 200 bonus
is_correct = True
else:
if distance_km > MAX_DISTANCE_KM:
# Beyond max distance - 0 points
base_score = 0
is_correct = False
else:
# Non-linear decrease using exponential falloff
# This creates a steeper drop-off at the beginning
progress = (distance_km - tolerance_km) / (MAX_DISTANCE_KM - tolerance_km)
# Using a power function to create non-linear falloff
falloff = progress ** 1.5 # 1.5 makes the falloff steeper at the beginning
base_score = 1000 * (1 - falloff)
is_correct = True
base_score = max(0, min(1000, int(base_score)))
# Apply time penalty (reduces score based on time taken)
if is_correct and base_score > 0:
# Calculate time taken as a fraction of total time (0-1)
time_taken_sec = (int(current_question.time_per_question) * 1000 - time_remaining) / 1000
max_time_sec = int(current_question.time_per_question)
time_fraction = min(1.0, time_taken_sec / max_time_sec) if max_time_sec > 0 else 1.0
# Reduce points based on time taken (up to 50% reduction for answering at the last second)
time_penalty = base_score * 0.5 * time_fraction # Up to 50% penalty
final_score = max(1, base_score - time_penalty) # At least 1 point if correct
else:
final_score = base_score # 0 points for incorrect answers
score = int(round(final_score))
# Save the answer
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 save_answer(self, participant_id, answer, time_remaining):
try: