First Iteration of Map Question Type
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user