✨ Quiz is no loner stored in player's browser and more improvements under the hood
This commit is contained in:
@@ -159,6 +159,7 @@ class PlayGame(BaseModel):
|
|||||||
captcha_enabled: bool = False
|
captcha_enabled: bool = False
|
||||||
cover_image: str | None
|
cover_image: str | None
|
||||||
game_mode: str | None
|
game_mode: str | None
|
||||||
|
current_question: int = -1
|
||||||
|
|
||||||
|
|
||||||
class GamePlayer(BaseModel):
|
class GamePlayer(BaseModel):
|
||||||
@@ -180,7 +181,7 @@ class GameAnswer1(BaseModel):
|
|||||||
class GameSession(BaseModel):
|
class GameSession(BaseModel):
|
||||||
admin: str
|
admin: str
|
||||||
game_id: str
|
game_id: str
|
||||||
players: list[GamePlayer | None]
|
# players: list[GamePlayer | None]
|
||||||
answers: list[GameAnswer1 | None]
|
answers: list[GameAnswer1 | None]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ async def start_quiz(
|
|||||||
cover_image=quiz.cover_image,
|
cover_image=quiz.cover_image,
|
||||||
game_mode=game_mode,
|
game_mode=game_mode,
|
||||||
)
|
)
|
||||||
await redis.set(f"game:{str(game.game_pin)}", (game.json()), ex=18000)
|
await redis.set(f"game:{str(game.game_pin)}", game.json(), ex=18000)
|
||||||
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
|
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import os
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import socketio
|
import socketio
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
from classquiz.config import redis, settings
|
from classquiz.config import redis, settings
|
||||||
from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer
|
from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer, RangeQuizAnswer, QuizQuestion
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError, validator
|
||||||
|
|
||||||
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins=[])
|
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins=[])
|
||||||
settings = settings()
|
settings = settings()
|
||||||
@@ -22,7 +21,7 @@ async def generate_final_results(game_data: PlayGame, game_pin: str) -> dict:
|
|||||||
for i in range(len(game_data.questions)):
|
for i in range(len(game_data.questions)):
|
||||||
redis_res = await redis.get(f"game_session:{game_pin}:{i}")
|
redis_res = await redis.get(f"game_session:{game_pin}:{i}")
|
||||||
if redis_res is None:
|
if redis_res is None:
|
||||||
break
|
continue
|
||||||
else:
|
else:
|
||||||
results[str(i)] = json.loads(redis_res)
|
results[str(i)] = json.loads(redis_res)
|
||||||
return results
|
return results
|
||||||
@@ -46,10 +45,14 @@ async def join_game(sid: str, data: dict):
|
|||||||
await sio.emit("error", room=sid)
|
await sio.emit("error", room=sid)
|
||||||
print(e)
|
print(e)
|
||||||
return
|
return
|
||||||
|
game_data = PlayGame.parse_raw(redis_res)
|
||||||
|
if game_data.started:
|
||||||
|
await sio.emit("game_already_started", room=sid)
|
||||||
|
return
|
||||||
# +++ START checking captcha +++
|
# +++ START checking captcha +++
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
try:
|
try:
|
||||||
if json.loads(redis_res)["captcha_enabled"]:
|
if game_data.captcha_enabled:
|
||||||
try:
|
try:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
"https://hcaptcha.com/siteverify",
|
"https://hcaptcha.com/siteverify",
|
||||||
@@ -72,15 +75,19 @@ async def join_game(sid: str, data: dict):
|
|||||||
"admin": False,
|
"admin": False,
|
||||||
}
|
}
|
||||||
await sio.save_session(sid, session)
|
await sio.save_session(sid, session)
|
||||||
await sio.emit("joined_game", redis_res, room=sid)
|
await sio.emit(
|
||||||
|
"joined_game",
|
||||||
|
{**json.loads(game_data.json(exclude={"quiz_id", "questions"})), "question_count": len(game_data.questions)},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
redis_res = await redis.get(f"game_session:{data.game_pin}")
|
redis_res = await redis.get(f"game_session:{data.game_pin}")
|
||||||
redis_res = GameSession.parse_raw(redis_res)
|
redis_res = GameSession.parse_raw(redis_res)
|
||||||
redis_res.players.append(GamePlayer(username=data.username, sid=sid))
|
await redis.sadd(f"game_session:{data.game_pin}:players", GamePlayer(username=data.username, sid=sid).json())
|
||||||
await redis.set(
|
# await redis.set(
|
||||||
f"game_session:{data.game_pin}",
|
# f"game_session:{data.game_pin}",
|
||||||
GameSession(admin=redis_res.admin, game_id=redis_res.game_id, players=redis_res.players, answers=[]).json(),
|
# GameSession(admin=redis_res.admin, game_id=redis_res.game_id, answers=[]).json(),
|
||||||
ex=18000,
|
# ex=18000,
|
||||||
)
|
# )
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"player_joined",
|
"player_joined",
|
||||||
{"username": data.username, "sid": sid},
|
{"username": data.username, "sid": sid},
|
||||||
@@ -93,6 +100,9 @@ async def join_game(sid: str, data: dict):
|
|||||||
async def start_game(sid: str, _data: dict):
|
async def start_game(sid: str, _data: dict):
|
||||||
session = await sio.get_session(sid)
|
session = await sio.get_session(sid)
|
||||||
if session["admin"]:
|
if session["admin"]:
|
||||||
|
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
|
||||||
|
game_data.started = True
|
||||||
|
await redis.set(f"game:{session['game_pin']}", game_data.json())
|
||||||
await sio.emit("start_game", room=session["game_pin"])
|
await sio.emit("start_game", room=session["game_pin"])
|
||||||
|
|
||||||
|
|
||||||
@@ -114,7 +124,7 @@ async def register_as_admin(sid: str, data: dict):
|
|||||||
if (await redis.get(f"game_session:{game_pin}")) is None:
|
if (await redis.get(f"game_session:{game_pin}")) is None:
|
||||||
await redis.set(
|
await redis.set(
|
||||||
f"game_session:{game_pin}",
|
f"game_session:{game_pin}",
|
||||||
GameSession(admin=sid, game_id=game_id, answers=[], players=[]).json(),
|
GameSession(admin=sid, game_id=game_id, answers=[]).json(),
|
||||||
ex=18000,
|
ex=18000,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,13 +150,45 @@ async def get_question_results(sid: str, data: dict):
|
|||||||
await sio.emit("question_results", redis_res, room=game_pin)
|
await sio.emit("question_results", redis_res, room=game_pin)
|
||||||
|
|
||||||
|
|
||||||
|
class ABCDQuizAnswerWithoutSolution(BaseModel):
|
||||||
|
answer: str
|
||||||
|
color: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class RangeQuizAnswerWithoutSolution(BaseModel):
|
||||||
|
min: int
|
||||||
|
max: int
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnQuestion(QuizQuestion):
|
||||||
|
answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution
|
||||||
|
|
||||||
|
@validator("answers")
|
||||||
|
def answers_not_none_if_abcd_type(cls, v, values):
|
||||||
|
if values["type"] == QuizQuestionType.ABCD and len(v) == 0:
|
||||||
|
raise ValueError("Answers can't be none if type is ABCD")
|
||||||
|
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution:
|
||||||
|
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
async def set_question_number(sid, data: str):
|
async def set_question_number(sid, data: str):
|
||||||
# data is just a number (as a str) of the question
|
# data is just a number (as a str) of the question
|
||||||
session = await sio.get_session(sid)
|
session = await sio.get_session(sid)
|
||||||
if session["admin"]:
|
if session["admin"]:
|
||||||
game_pin = session["game_pin"]
|
game_pin = session["game_pin"]
|
||||||
await sio.emit("set_question_number", data, room=game_pin)
|
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
|
||||||
|
game_data.current_question = int(data)
|
||||||
|
await redis.set(f"game:{session['game_pin']}", game_data.json())
|
||||||
|
await sio.emit(
|
||||||
|
"set_question_number",
|
||||||
|
{
|
||||||
|
"question_index": int(data),
|
||||||
|
"question": ReturnQuestion(**game_data.dict(include={"questions"})["questions"][int(data)]).dict(),
|
||||||
|
},
|
||||||
|
room=game_pin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _SubmitAnswerData(BaseModel):
|
class _SubmitAnswerData(BaseModel):
|
||||||
@@ -219,6 +261,7 @@ async def get_final_results(sid: str, _data: dict):
|
|||||||
if not session["admin"]:
|
if not session["admin"]:
|
||||||
return
|
return
|
||||||
results = await generate_final_results(game_data, session["game_pin"])
|
results = await generate_final_results(game_data, session["game_pin"])
|
||||||
|
print(results)
|
||||||
await sio.emit("final_results", results, room=session["game_pin"])
|
await sio.emit("final_results", results, room=session["game_pin"])
|
||||||
|
|
||||||
|
|
||||||
@@ -232,3 +275,12 @@ async def get_export_token(sid):
|
|||||||
token = os.urandom(32).hex()
|
token = os.urandom(32).hex()
|
||||||
await redis.set(f"export_token:{token}", json.dumps(results))
|
await redis.set(f"export_token:{token}", json.dumps(results))
|
||||||
await sio.emit("export_token", token, room=sid)
|
await sio.emit("export_token", token, room=sid)
|
||||||
|
|
||||||
|
|
||||||
|
@sio.event
|
||||||
|
async def show_solutions(sid: str, _data: dict):
|
||||||
|
session: dict = await sio.get_session(sid)
|
||||||
|
game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}")))
|
||||||
|
if not session["admin"]:
|
||||||
|
return
|
||||||
|
await sio.emit("solutions", game_data.questions[game_data.current_question].dict(), room=session["game_pin"])
|
||||||
|
|||||||
@@ -43,6 +43,10 @@
|
|||||||
});
|
});
|
||||||
timer_res = '0';
|
timer_res = '0';
|
||||||
};
|
};
|
||||||
|
const show_solutions = () => {
|
||||||
|
socket.emit('show_solutions', {});
|
||||||
|
timer_res = '0';
|
||||||
|
};
|
||||||
|
|
||||||
const get_final_results = () => {
|
const get_final_results = () => {
|
||||||
socket.emit('get_final_results', {});
|
socket.emit('get_final_results', {});
|
||||||
@@ -67,6 +71,7 @@
|
|||||||
let timer_interval = setInterval(() => {
|
let timer_interval = setInterval(() => {
|
||||||
if (timer_res === '0') {
|
if (timer_res === '0') {
|
||||||
clearInterval(timer_interval);
|
clearInterval(timer_interval);
|
||||||
|
socket.emit('show_solutions', {});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
seconds--;
|
seconds--;
|
||||||
@@ -85,6 +90,7 @@
|
|||||||
circular_prgoress = 0;
|
circular_prgoress = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: console.log(quiz_data.questions.length, 'length of quiz');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if game_mode === 'kahoot'}
|
{#if game_mode === 'kahoot'}
|
||||||
@@ -100,6 +106,24 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3">
|
<div class="justify-self-end ml-auto mr-0 col-start-3 col-end-3">
|
||||||
|
{#if selected_question + 1 === quiz_data.questions.length && timer_res === '0'}
|
||||||
|
{#if JSON.stringify(final_results) === JSON.stringify([null])}
|
||||||
|
<button on:click={get_final_results}>Get final results</button>
|
||||||
|
{/if}
|
||||||
|
{:else if timer_res === '0' || selected_question === -1}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
set_question_number(selected_question + 1);
|
||||||
|
}}
|
||||||
|
>Next Question ({selected_question + 2})
|
||||||
|
</button>
|
||||||
|
{#if question_results === null && selected_question !== -1}
|
||||||
|
<button on:click={get_question_results}>Show results</button>
|
||||||
|
{/if}
|
||||||
|
{:else if selected_question !== -1}
|
||||||
|
<button on:click={show_solutions}>Stop time and show solutions</button>
|
||||||
|
{:else}
|
||||||
|
<p>!OK!</p>
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
set_question_number(selected_question + 1);
|
set_question_number(selected_question + 1);
|
||||||
@@ -107,6 +131,7 @@
|
|||||||
>Next Question ({selected_question + 2}
|
>Next Question ({selected_question + 2}
|
||||||
)
|
)
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if timer_res !== '0' && selected_question >= 0}
|
{#if timer_res !== '0' && selected_question >= 0}
|
||||||
@@ -228,10 +253,12 @@
|
|||||||
{#if game_mode === 'normal'}
|
{#if game_mode === 'normal'}
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
<button
|
<button
|
||||||
on:click={get_question_results}
|
on:click={() => {
|
||||||
|
timer_res = '0';
|
||||||
|
}}
|
||||||
id="GetQuestionResultsAndStopTime"
|
id="GetQuestionResultsAndStopTime"
|
||||||
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
class="px-4 py-2 leading-5 text-white transition-colors duration-200 transform bg-gray-700 rounded text-center hover:bg-gray-600 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>{$t('admin_page.get_results_and_stop_time')}</button
|
>{$t('admin_page.stop_time')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
const { t } = getLocalization();
|
const { t } = getLocalization();
|
||||||
|
|
||||||
export let quiz_data: QuizData;
|
export let question_count: number;
|
||||||
export let final_results: Array<null> | Array<Array<PlayerAnswer>>;
|
export let final_results: Array<null> | Array<Array<PlayerAnswer>>;
|
||||||
|
|
||||||
interface PlayerAnswer {
|
interface PlayerAnswer {
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
const getWinnersSorted = () => {
|
const getWinnersSorted = () => {
|
||||||
let winners = {};
|
let winners = {};
|
||||||
let q_count = quiz_data.questions.length;
|
let q_count = question_count;
|
||||||
console.log(
|
console.log(
|
||||||
JSON.stringify(final_results),
|
JSON.stringify(final_results),
|
||||||
JSON.stringify(final_results) === '{}',
|
JSON.stringify(final_results) === '{}',
|
||||||
@@ -42,9 +42,15 @@
|
|||||||
try {
|
try {
|
||||||
for (let i = 0; i < q_count; i++) {
|
for (let i = 0; i < q_count; i++) {
|
||||||
let q_res = final_results[i];
|
let q_res = final_results[i];
|
||||||
if (q_res === null) {
|
if (!q_res) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
q_res = final_results[String(i)];
|
||||||
|
if (!q_res) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
console.log(q_res);
|
||||||
for (let j = 0; j < q_res.length; j++) {
|
for (let j = 0; j < q_res.length; j++) {
|
||||||
let res = q_res[j];
|
let res = q_res[j];
|
||||||
if (res['right']) {
|
if (res['right']) {
|
||||||
@@ -68,6 +74,7 @@
|
|||||||
data_available = true;
|
data_available = true;
|
||||||
return close_to_res;
|
return close_to_res;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
data_available = false;
|
data_available = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,7 +100,7 @@
|
|||||||
<span
|
<span
|
||||||
>{$t('play_page.with_out_of', {
|
>{$t('play_page.with_out_of', {
|
||||||
correct_questions: winners_arr[0][1] ?? 0,
|
correct_questions: winners_arr[0][1] ?? 0,
|
||||||
total_question_count: quiz_data.questions.length
|
total_question_count: question_count
|
||||||
})}</span
|
})}</span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
@@ -106,7 +113,7 @@
|
|||||||
<span
|
<span
|
||||||
>{$t('play_page.with_out_of', {
|
>{$t('play_page.with_out_of', {
|
||||||
correct_questions: winners_arr[1][1] ?? 0,
|
correct_questions: winners_arr[1][1] ?? 0,
|
||||||
total_question_count: quiz_data.questions.length
|
total_question_count: question_count
|
||||||
})}</span
|
})}</span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
@@ -120,7 +127,7 @@
|
|||||||
<span>
|
<span>
|
||||||
{$t('play_page.with_out_of', {
|
{$t('play_page.with_out_of', {
|
||||||
correct_questions: winners_arr[2][1] ?? 0,
|
correct_questions: winners_arr[2][1] ?? 0,
|
||||||
total_question_count: quiz_data.questions.length
|
total_question_count: question_count
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -17,12 +17,22 @@
|
|||||||
export let question: Question;
|
export let question: Question;
|
||||||
export let game_mode;
|
export let game_mode;
|
||||||
export let question_index: string | number;
|
export let question_index: string | number;
|
||||||
|
export let solution;
|
||||||
|
|
||||||
if (typeof question_index === 'string') {
|
$: console.log(question_index, question, 'hi!');
|
||||||
|
|
||||||
|
console.log(question);
|
||||||
|
if (question.type === undefined) {
|
||||||
|
question.type = QuizQuestionType.ABCD;
|
||||||
|
} else {
|
||||||
|
question.type = QuizQuestionType[question.type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if (typeof question_index === 'string') {
|
||||||
question_index = parseInt(question_index);
|
question_index = parseInt(question_index);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('question_index must be a string or number');
|
throw new Error('question_index must be a string or number');
|
||||||
}
|
}*/
|
||||||
|
|
||||||
let timer_res = question.time;
|
let timer_res = question.time;
|
||||||
let selected_answer: string;
|
let selected_answer: string;
|
||||||
@@ -44,6 +54,12 @@
|
|||||||
|
|
||||||
timer(question.time);
|
timer(question.time);
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (solution !== undefined) {
|
||||||
|
timer_res = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const selectAnswer = (answer: string) => {
|
const selectAnswer = (answer: string) => {
|
||||||
selected_answer = answer;
|
selected_answer = answer;
|
||||||
//timer_res = '0';
|
//timer_res = '0';
|
||||||
@@ -75,6 +91,8 @@
|
|||||||
circular_prgoress = 0;
|
circular_prgoress = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: console.log(solution);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col justify-center w-screen h-1/6">
|
<div class="flex flex-col justify-center w-screen h-1/6">
|
||||||
@@ -149,8 +167,11 @@
|
|||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
{:else if question.type === QuizQuestionType.ABCD}
|
{:else if question.type === QuizQuestionType.ABCD}
|
||||||
|
{#if solution === undefined}
|
||||||
|
<Spinner />
|
||||||
|
{:else}
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
{#each question.answers as answer}
|
{#each solution.answers as answer}
|
||||||
{#if answer.right}
|
{#if answer.right}
|
||||||
<button
|
<button
|
||||||
class="w-1/2 text-3xl bg-green-600 border border-white"
|
class="w-1/2 text-3xl bg-green-600 border border-white"
|
||||||
@@ -166,14 +187,19 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{:else if question.type === QuizQuestionType.RANGE}
|
{:else if question.type === QuizQuestionType.RANGE}
|
||||||
|
{#if solution === undefined}
|
||||||
|
<Spinner />
|
||||||
|
{:else}
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
Every number between {question.answers.min_correct} and {question.answers.max_correct} was correct.
|
Every number between {solution.answers.min_correct} and {solution.answers.max_correct} was
|
||||||
You got {selected_answer}, so you have been
|
correct. You got {selected_answer}, so you have been
|
||||||
{#if question.answers.min_correct <= parseInt(selected_answer) && parseInt(selected_answer) <= question.answers.max_correct}
|
{#if solution.answers.min_correct <= parseInt(selected_answer) && parseInt(selected_answer) <= solution.answers.max_correct}
|
||||||
correct
|
correct
|
||||||
{:else}
|
{:else}
|
||||||
wrong.
|
wrong.
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Answer, QuizData } from '$lib/quiz_types';
|
import type { Answer, Question, QuizData } from '$lib/quiz_types';
|
||||||
import { getLocalization } from '$lib/i18n';
|
import { getLocalization } from '$lib/i18n';
|
||||||
import { QuizQuestionType } from '../quiz_types.js';
|
import { QuizQuestionType } from '../quiz_types.js';
|
||||||
import Spinner from '$lib/Spinner.svelte';
|
import Spinner from '$lib/Spinner.svelte';
|
||||||
@@ -12,10 +12,11 @@
|
|||||||
const { t } = getLocalization();
|
const { t } = getLocalization();
|
||||||
export let results: Array<Answer>;
|
export let results: Array<Answer>;
|
||||||
export let game_data: QuizData;
|
export let game_data: QuizData;
|
||||||
|
export let solution: Question;
|
||||||
export let question_index: string;
|
export let question_index: string;
|
||||||
let data_store = {};
|
let data_store = {};
|
||||||
|
|
||||||
const question = game_data.questions[parseInt(question_index)].answers;
|
const question = solution.answers;
|
||||||
for (let i = 0; i < question.length; i++) {
|
for (let i = 0; i < question.length; i++) {
|
||||||
data_store[question[i].answer] = 0;
|
data_store[question[i].answer] = 0;
|
||||||
}
|
}
|
||||||
@@ -24,11 +25,8 @@
|
|||||||
data_store[results[i].answer] += 1;
|
data_store[results[i].answer] += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let slider_values = [
|
let slider_values = [solution.answers.min_correct ?? 0, solution.answers.max_correct ?? 0];
|
||||||
game_data.questions[parseInt(question_index)].answers.min_correct ?? 0,
|
console.log(slider_values, solution.answers);
|
||||||
game_data.questions[parseInt(question_index)].answers.max_correct ?? 0
|
|
||||||
];
|
|
||||||
console.log(slider_values, game_data.questions[parseInt(question_index)].answers);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Show the results from the results object -->
|
<!-- Show the results from the results object -->
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-center text-3xl mb-8">{$t('words.result', { count: 2 })}</h2>
|
<h2 class="text-center text-3xl mb-8">{$t('words.result', { count: 2 })}</h2>
|
||||||
<div class="w-screen flex justify-center">
|
<div class="w-screen flex justify-center">
|
||||||
{#if game_data.questions[parseInt(question_index)].type === QuizQuestionType.ABCD}
|
{#if solution.type === QuizQuestionType.ABCD}
|
||||||
<div class="relative overflow-x-auto shadow-md rounded-lg">
|
<div class="relative overflow-x-auto shadow-md rounded-lg">
|
||||||
<table class="w-fit text-sm text-left text-gray-500 dark:text-gray-400">
|
<table class="w-fit text-sm text-left text-gray-500 dark:text-gray-400">
|
||||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||||
@@ -64,7 +62,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each game_data.questions[parseInt(question_index)].answers as answer}
|
{#each solution.answers as answer}
|
||||||
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
||||||
<td
|
<td
|
||||||
class="py-4 px-6 text-sm font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
class="py-4 px-6 text-sm font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||||
@@ -90,10 +88,10 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{:else if game_data.questions[parseInt(question_index)].type === QuizQuestionType.RANGE}
|
{:else if solution.type === QuizQuestionType.RANGE}
|
||||||
<!--<p class="text-center">
|
<!--<p class="text-center">
|
||||||
Every number between {game_data.questions[parseInt(question_index)].answers
|
Every number between {solution.answers
|
||||||
.min_correct} and {game_data.questions[parseInt(question_index)].answers
|
.min_correct} and {solution.answers
|
||||||
.max_correct} was correct.
|
.max_correct} was correct.
|
||||||
</p>-->
|
</p>-->
|
||||||
{#await import('svelte-range-slider-pips')}
|
{#await import('svelte-range-slider-pips')}
|
||||||
@@ -103,8 +101,8 @@
|
|||||||
<svelte:component
|
<svelte:component
|
||||||
this={c.default}
|
this={c.default}
|
||||||
bind:values={slider_values}
|
bind:values={slider_values}
|
||||||
bind:min={game_data.questions[parseInt(question_index)].answers.min}
|
bind:min={solution.answers.min}
|
||||||
bind:max={game_data.questions[parseInt(question_index)].answers.max}
|
bind:max={solution.answers.max}
|
||||||
pips
|
pips
|
||||||
float
|
float
|
||||||
all="label"
|
all="label"
|
||||||
|
|||||||
@@ -119,7 +119,11 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{#await import('$lib/play/end.svelte') then c}
|
{#await import('$lib/play/end.svelte') then c}
|
||||||
<svelte:component this={c.default} bind:final_results bind:quiz_data />
|
<svelte:component
|
||||||
|
this={c.default}
|
||||||
|
bind:final_results
|
||||||
|
bind:question_count={quiz_data.questions.length}
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
{#if !success}
|
{#if !success}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { socket } from '$lib/socket';
|
import { socket } from '$lib/socket';
|
||||||
import JoinGame from '$lib/play/join.svelte';
|
import JoinGame from '$lib/play/join.svelte';
|
||||||
import type { Answer, QuizData } from '$lib/quiz_types';
|
import type { Answer, QuizData, Question } from '$lib/quiz_types';
|
||||||
import ShowTitle from '$lib/play/title.svelte';
|
import ShowTitle from '$lib/play/title.svelte';
|
||||||
import Question from '$lib/play/question.svelte';
|
import Question from '$lib/play/question.svelte';
|
||||||
import ShowResults from '$lib/play/show_results.svelte';
|
import ShowResults from '$lib/play/show_results.svelte';
|
||||||
@@ -37,11 +37,14 @@
|
|||||||
navbarVisible.set(false);
|
navbarVisible.set(false);
|
||||||
let game_pin_valid: boolean;
|
let game_pin_valid: boolean;
|
||||||
let answer_results: Array<Answer>;
|
let answer_results: Array<Answer>;
|
||||||
let gameData: QuizData;
|
let gameData;
|
||||||
|
let solution: Question;
|
||||||
let gameMeta: GameMeta = {
|
let gameMeta: GameMeta = {
|
||||||
started: false
|
started: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let question;
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
function restart() {
|
function restart() {
|
||||||
unique = {};
|
unique = {};
|
||||||
@@ -56,17 +59,7 @@
|
|||||||
|
|
||||||
// Socket-events
|
// Socket-events
|
||||||
socket.on('joined_game', (data) => {
|
socket.on('joined_game', (data) => {
|
||||||
console.log('joined_game', data);
|
gameData = data;
|
||||||
let temp_data = JSON.parse(data);
|
|
||||||
for (let i = 0; i < temp_data.questions.length; i++) {
|
|
||||||
let question = temp_data.questions[i];
|
|
||||||
if (question.type === undefined) {
|
|
||||||
temp_data.questions[i].type = QuizQuestionType.ABCD;
|
|
||||||
} else {
|
|
||||||
temp_data.questions[i].type = QuizQuestionType[question.type];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gameData = temp_data;
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
plausible('Joined Game', { props: { quiz_id: gameData.quiz_id } });
|
plausible('Joined Game', { props: { quiz_id: gameData.quiz_id } });
|
||||||
});
|
});
|
||||||
@@ -76,9 +69,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('set_question_number', (data) => {
|
socket.on('set_question_number', (data) => {
|
||||||
|
solution = undefined;
|
||||||
restart();
|
restart();
|
||||||
|
console.log(data, data.question_index);
|
||||||
|
question = data.question;
|
||||||
|
question_index = data.question_index;
|
||||||
answer_results = undefined;
|
answer_results = undefined;
|
||||||
question_index = data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('start_game', () => {
|
socket.on('start_game', () => {
|
||||||
@@ -97,6 +93,10 @@
|
|||||||
socket.on('final_results', (data) => {
|
socket.on('final_results', (data) => {
|
||||||
final_results = data;
|
final_results = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('solutions', (data) => {
|
||||||
|
solution = data;
|
||||||
|
});
|
||||||
// The rest
|
// The rest
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -109,19 +109,19 @@
|
|||||||
<svelte:window on:beforeunload={confirmUnload} />
|
<svelte:window on:beforeunload={confirmUnload} />
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>ClassQuiz - Play</title>
|
<title>ClassQuiz - Play</title>
|
||||||
{#if gameData !== undefined && game_mode !== 'kahoot'}
|
<!-- {#if gameData !== undefined && game_mode !== 'kahoot'}
|
||||||
{#each gameData.questions as question}
|
{#each gameData.questions as question}
|
||||||
{#if question.image !== undefined}
|
{#if question.image !== undefined}
|
||||||
<link rel="preload" as="image" href={question.image} />
|
<link rel="preload" as="image" href={question.image} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}-->
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<div>
|
<div>
|
||||||
{#if !gameMeta.started && gameData === undefined}
|
{#if !gameMeta.started && gameData === undefined}
|
||||||
<JoinGame {game_pin} bind:game_mode />
|
<JoinGame {game_pin} bind:game_mode />
|
||||||
{:else if JSON.stringify(final_results) !== JSON.stringify([null])}
|
{:else if JSON.stringify(final_results) !== JSON.stringify([null])}
|
||||||
<ShowEndScreen bind:final_results bind:quiz_data={gameData} />
|
<ShowEndScreen bind:final_results bind:question_count={gameData.question_count} />
|
||||||
{:else if gameData !== undefined && question_index === ''}
|
{:else if gameData !== undefined && question_index === ''}
|
||||||
<ShowTitle
|
<ShowTitle
|
||||||
bind:title={gameData.title}
|
bind:title={gameData.title}
|
||||||
@@ -130,11 +130,7 @@
|
|||||||
/>
|
/>
|
||||||
{:else if gameMeta.started && gameData !== undefined && question_index !== '' && answer_results === undefined}
|
{:else if gameMeta.started && gameData !== undefined && question_index !== '' && answer_results === undefined}
|
||||||
{#key unique}
|
{#key unique}
|
||||||
<Question
|
<Question bind:game_mode bind:question bind:question_index bind:solution />
|
||||||
bind:game_mode
|
|
||||||
bind:question={gameData.questions[parseInt(question_index)]}
|
|
||||||
bind:question_index
|
|
||||||
/>
|
|
||||||
{/key}
|
{/key}
|
||||||
{:else if gameMeta.started && answer_results !== undefined}
|
{:else if gameMeta.started && answer_results !== undefined}
|
||||||
{#if answer_results === null}
|
{#if answer_results === null}
|
||||||
@@ -147,6 +143,7 @@
|
|||||||
bind:results={answer_results}
|
bind:results={answer_results}
|
||||||
bind:game_data={gameData}
|
bind:game_data={gameData}
|
||||||
bind:question_index
|
bind:question_index
|
||||||
|
bind:solution
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user