Files
classquiz-ai/classquiz/routers/box_controller/embedded/socket.py
T
2023-06-30 13:16:12 +02:00

129 lines
4.5 KiB
Python

# SPDX-FileCopyrightText: 2023 Marlon W (Mawoka)
#
# SPDX-License-Identifier: MPL-2.0
import enum
import typing
from datetime import datetime
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect, status
from pydantic import BaseModel, ValidationError
from classquiz.config import redis
from classquiz.db.models import PlayGame, QuizQuestionType, AnswerData, GamePlayer
from classquiz.socket_server import sio, calculate_score, set_answer
router = APIRouter()
class SubmitAnswerInput(BaseModel):
answer: int
async def submit_answer_fn(data_answer: int, game_pin: str, player_id: str, now: datetime):
redis_res_game = await redis.get(f"game:{game_pin}")
username = await redis.get(f"game:cqc:player:{player_id}")
if redis_res_game is None or username is None:
raise HTTPException(status_code=404, detail="id not existent")
game = PlayGame.parse_raw(redis_res_game)
if not game.question_show:
return
question = game.questions[game.current_question]
question_time = datetime.fromisoformat(await redis.get(f"game:{game_pin}:current_time"))
try:
selected_answer = question.answers[data_answer].answer
except KeyError:
return
answer_right = False
if question.type == QuizQuestionType.ABCD:
for answer in question.answers:
if answer.answer == selected_answer and answer.right:
answer_right = True
break
elif question.type == QuizQuestionType.VOTING:
answer_right = False
else:
return
diff = (question_time - now).total_seconds() * 1000
score = 0
if answer_right:
score = calculate_score(abs(diff), int(float(question.time)))
await redis.hincrby(f"game_session:{game_pin}:player_scores", username, score)
answer_data = AnswerData(
username=username,
answer=selected_answer,
right=answer_right,
time_taken=abs(diff),
score=score,
)
answers = await redis.get(f"game_session:{game_pin}:{game.current_question}")
answers = await set_answer(answers, game_pin=game_pin, data=answer_data, q_index=game.current_question)
player_count = await redis.scard(f"game_session:{game_pin}:players")
await sio.emit("player_answer", {})
if answers is not None and len(answers.__root__) == player_count:
await sio.emit("everyone_answered", {})
button_to_index_map = {"y": 0, "r": 3, "g": 1, "b": 2}
class WebSocketTypes(enum.Enum):
ButtonPress = "bp"
Error = "e"
class WebSocketRequest(BaseModel):
type: WebSocketTypes
data: typing.Any
wss_clients = {}
@router.websocket("/{game_id}")
async def websocket_endpoint(ws: WebSocket, game_id: str):
try:
if game_id in wss_clients:
await ws.close(code=status.WS_1001_GOING_AWAY)
print(f"Client {game_id} already exists.")
return
await ws.accept()
wss_clients[game_id] = ws
player_id, game_pin = game_id.split(":")
if player_id is None or game_pin is None:
await ws.send_text(WebSocketRequest(type=WebSocketTypes.Error, data="BadId").json())
await ws.close(code=status.WS_1003_UNSUPPORTED_DATA)
username = await redis.get(f"game:cqc:player:{player_id}")
await sio.emit(
"player_joined",
{"username": username, "sid": None},
room=f"admin:{game_pin}",
)
await redis.sadd(f"game_session:{game_pin}:players", GamePlayer(username=username, sid=None).json())
while True:
raw_data = await ws.receive_text()
try:
data = WebSocketRequest.parse_raw(raw_data)
except ValidationError as e:
print("ValError")
print(e)
print(raw_data)
await ws.send_text(WebSocketRequest(type=WebSocketTypes.Error, data="ValidationError").json())
continue
if data.type == WebSocketTypes.ButtonPress:
now = datetime.now()
try:
answer_index = button_to_index_map[data.data.lower()]
except (KeyError, AttributeError):
await ws.send_text(WebSocketRequest(type=WebSocketTypes.Error, data="InvalidButton").json())
continue
await submit_answer_fn(answer_index, game_pin, player_id, now)
print(f"Data from client {game_id}: {data}")
except WebSocketDisconnect as ex:
print(f"Client {game_id} is disconnected: {ex}")
wss_clients.pop(game_id, None)