🚧 Changed GameState to make it dynamic with svelte components and cleaned code

This commit is contained in:
Proxyfil
2026-04-22 23:29:25 +02:00
parent e612382eed
commit 749fb72d5d
5 changed files with 139 additions and 181 deletions
+30 -71
View File
@@ -5,10 +5,7 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import type { QuizData } from '$lib/quiz_types';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { get_question_title } from '$lib/admin.ts';
import type { PlayerAnswer } from '$lib/admin.ts';
import { socket } from './socket'; import { socket } from './socket';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import Spinner from '$lib/Spinner.svelte'; import Spinner from '$lib/Spinner.svelte';
@@ -20,31 +17,18 @@ SPDX-License-Identifier: MPL-2.0
const { t } = getLocalization(); const { t } = getLocalization();
const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58'];
let question_results = $state(null);
let selected_question = $state(-1);
let timer_res: string = $state();
let shown_question_now: number = $state();
let final_results_clicked = $state(false); let final_results_clicked = $state(false);
let timer_interval: NodeJS.Timeout; let timer_interval: NodeJS.Timeout;
let answer_count = $state(0);
interface Props { interface Props {
game_token: string; game_token: string;
quiz_data: QuizData;
bg_color: string; bg_color: string;
final_results?: Array<null> | Array<Array<PlayerAnswer>>;
control_visible: boolean;
player_scores: any;
game_state: GameState; game_state: GameState;
} }
let { let {
game_token, game_token,
quiz_data = $bindable(),
bg_color, bg_color,
final_results = $bindable([null]),
control_visible,
player_scores = $bindable(),
game_state = $bindable() game_state = $bindable()
}: Props = $props(); }: Props = $props();
@@ -52,70 +36,51 @@ SPDX-License-Identifier: MPL-2.0
console.log('get_question_results'); console.log('get_question_results');
}); });
socket.on('set_question_number', (data) => { socket.on('set_question_number', (data) => {
timer_res = '0';
clearInterval(timer_interval);
question_results = null;
shown_question_now = data.question_index;
timer_res = quiz_data.questions[data.question_index].time;
selected_question = selected_question + 1;
answer_count = 0;
timer(timer_res);
game_state.timer_res = '0'; game_state.timer_res = '0';
game_state.question_results = null; game_state.question_results = null;
game_state.shown_question_now = data.question_index; game_state.shown_question_now = data.question_index;
game_state.timer_res = quiz_data.questions[data.question_index].time; game_state.timer_res = game_state.quiz_data.questions[data.question_index].time;
game_state.selected_question = game_state.selected_question + 1; game_state.selected_question = game_state.selected_question + 1;
game_state.answer_count = 0; game_state.answer_count = 0;
clearInterval(timer_interval);
timer(game_state.timer_res);
}); });
socket.on('solutions', (_) => { socket.on('solutions', (_) => {
timer_res = '0';
clearInterval(timer_interval);
game_state.timer_res = '0'; game_state.timer_res = '0';
clearInterval(timer_interval);
}); });
socket.on('final_results', (data) => { socket.on('final_results', (data) => {
// data = JSON.parse(data);
final_results_clicked = true; final_results_clicked = true;
timer_res = '0';
final_results = data;
game_state.final_results = data;
game_state.timer_res = '0'; game_state.timer_res = '0';
game_state.final_results = data;
}); });
socket.on('everyone_answered', (_) => { socket.on('everyone_answered', (_) => {
timer_res = '0';
game_state.timer_res = '0'; game_state.timer_res = '0';
}); });
socket.on('question_results', (data) => { socket.on('question_results', (data) => {
console.log('question_results:', data);
question_results = data;
timer_res = '0';
game_state.question_results = data; game_state.question_results = data;
game_state.timer_res = '0'; game_state.timer_res = '0';
}); });
socket.on('player_answer', (_) => { socket.on('player_answer', (_) => {
answer_count += 1;
game_state.answer_count += 1; game_state.answer_count += 1;
}); });
const timer = (time: string) => { const timer = (time: string) => {
let seconds = Number(time); let seconds = Number(time);
timer_interval = setInterval(() => { timer_interval = setInterval(() => {
if (timer_res === '0') { if (game_state.timer_res === '0') {
clearInterval(timer_interval); clearInterval(timer_interval);
return; return;
} else { } else {
seconds--; seconds--;
} }
timer_res = seconds.toString();
game_state.timer_res = seconds.toString(); game_state.timer_res = seconds.toString();
}, 1000); }, 1000);
}; };
@@ -123,57 +88,51 @@ SPDX-License-Identifier: MPL-2.0
const socket_game_controls: SocketGameControls = new SocketGameControls(socket); const socket_game_controls: SocketGameControls = new SocketGameControls(socket);
</script> </script>
{#if control_visible} {#if game_state.control_visible}
<Controls <Controls
{bg_color} {bg_color}
{selected_question}
{quiz_data}
bind:timer_res
{final_results}
{socket_game_controls} {socket_game_controls}
{question_results}
{game_token} {game_token}
{shown_question_now}
bind:game_state bind:game_state
/> />
{/if} {/if}
{#if timer_res !== '0' && selected_question >= 0} {#if game_state.timer_res !== '0' && game_state.selected_question >= 0}
<span <span
class="fixed top-0 bg-red-500 h-8 transition-all" class="fixed top-0 bg-red-500 h-8 transition-all"
class:mt-10={control_visible} class:mt-10={game_state.control_visible}
style="width: {(100 / parseInt(quiz_data.questions[selected_question].time)) * style="width: {(100 / parseInt(game_state.quiz_data.questions[game_state.selected_question].time)) *
parseInt(timer_res)}vw" parseInt(game_state.timer_res)}vw"
></span> ></span>
{/if} {/if}
<div class="w-full h-full" class:pt-28={control_visible} class:pt-12={!control_visible}> <div class="w-full h-full" class:pt-28={game_state.control_visible} class:pt-12={!game_state.control_visible}>
{#if timer_res !== undefined && !final_results_clicked && !question_results} {#if game_state.timer_res !== undefined && !final_results_clicked && !game_state.question_results}
<!-- Question is shown --> <!-- Question is shown -->
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
{#await import('$lib/play/admin/slide.svelte')} {#await import('$lib/play/admin/slide.svelte')}
<Spinner my_20={false} /> <Spinner my_20={false} />
{:then c} {:then c}
<c.default question={quiz_data.questions[selected_question]} /> <c.default question={game_state.quiz_data.questions[game_state.selected_question]} />
{/await} {/await}
{:else} {:else}
<Question {quiz_data} {selected_question} {timer_res} {answer_count} {default_colors} /> <Question quiz_data={game_state.quiz_data} selected_question={game_state.selected_question} timer_res={game_state.timer_res} answer_count={game_state.answer_count} default_colors={default_colors} />
{/if} {/if}
{/if} {/if}
<br /> <br />
{#if timer_res === '0' && JSON.stringify(final_results) === JSON.stringify( [null] ) && quiz_data.questions[selected_question].type !== QuizQuestionType.SLIDE && question_results !== null && quiz_data.questions[selected_question]?.hide_results !== true} {#if game_state.timer_res === '0' && JSON.stringify(game_state.final_results) === JSON.stringify( [null] ) && game_state.quiz_data.questions[game_state.selected_question].type !== QuizQuestionType.SLIDE && game_state.question_results !== null && game_state.quiz_data.questions[game_state.selected_question]?.hide_results !== true}
{#if question_results === undefined} {#if game_state.question_results === undefined}
{#if !final_results_clicked} {#if !final_results_clicked}
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
<h1 class="text-3xl">{$t('admin_page.no_answers')}</h1> <h1 class="text-3xl">{$t('admin_page.no_answers')}</h1>
</div> </div>
{/if} {/if}
{:else if quiz_data.questions[selected_question].type === QuizQuestionType.VOTING} {:else if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.VOTING}
{#await import('$lib/play/admin/voting_results.svelte')} {#await import('$lib/play/admin/voting_results.svelte')}
<Spinner /> <Spinner />
{:then c} {:then c}
<c.default <c.default
data={question_results} data={game_state.question_results}
question={quiz_data.questions[selected_question]} question={game_state.quiz_data.questions[game_state.selected_question]}
/> />
{/await} {/await}
{:else} {:else}
@@ -181,24 +140,24 @@ SPDX-License-Identifier: MPL-2.0
<Spinner /> <Spinner />
{:then c} {:then c}
<c.default <c.default
bind:data={player_scores} bind:data={game_state.player_scores}
question={quiz_data.questions[selected_question]} question={game_state.quiz_data.questions[game_state.selected_question]}
new_data={question_results} new_data={game_state.question_results}
/> />
{/await} {/await}
{/if} {/if}
{/if} {/if}
<br /> <br />
{#if selected_question === -1} {#if game_state.selected_question === -1}
<div class="flex flex-col justify-center w-screen h-full"> <div class="flex flex-col justify-center w-screen h-full">
<h1 class="text-7xl text-center">{@html quiz_data.title}</h1> <h1 class="text-7xl text-center">{@html game_state.quiz_data.title}</h1>
<p class="text-3xl pt-8 text-center">{@html quiz_data.description}</p> <p class="text-3xl pt-8 text-center">{@html game_state.quiz_data.description}</p>
{#if quiz_data.cover_image} {#if game_state.quiz_data.cover_image}
<div class="flex justify-center align-middle items-center"> <div class="flex justify-center align-middle items-center">
<div class="h-[30vh] m-auto w-auto mt-12"> <div class="h-[30vh] m-auto w-auto mt-12">
<img <img
class="max-h-full max-w-full block" class="max-h-full max-w-full block"
src="/api/v1/storage/download/{quiz_data.cover_image}" src="/api/v1/storage/download/{game_state.quiz_data.cover_image}"
alt="Not provided" alt="Not provided"
/> />
</div> </div>
+21 -36
View File
@@ -6,35 +6,21 @@ SPDX-License-Identifier: MPL-2.0
<script lang="ts"> <script lang="ts">
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import type { QuizData } from '$lib/quiz_types';
import type { Socket } from 'socket.io-client';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts'; import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import { GameState } from '$lib/play/admin/game_state.ts'; import { GameState } from '$lib/play/admin/game_state.ts';
interface Props { interface Props {
bg_color: string; bg_color: string;
selected_question: number;
quiz_data: QuizData;
timer_res: string;
final_results: any;
socket_game_controls: SocketGameControls; socket_game_controls: SocketGameControls;
game_token: string; game_token: string;
question_results: any;
shown_question_now: number;
game_state: GameState; game_state: GameState;
} }
let { let {
bg_color, bg_color,
selected_question,
quiz_data,
timer_res = $bindable(),
final_results,
socket_game_controls, socket_game_controls,
game_token, game_token,
question_results,
shown_question_now,
game_state = $bindable() game_state = $bindable()
}: Props = $props(); }: Props = $props();
@@ -42,7 +28,6 @@ SPDX-License-Identifier: MPL-2.0
const show_solutions = () => { const show_solutions = () => {
socket_game_controls.show_solutions(); socket_game_controls.show_solutions();
timer_res = '0';
game_state.timer_res = '0'; game_state.timer_res = '0';
}; };
</script> </script>
@@ -53,60 +38,60 @@ SPDX-License-Identifier: MPL-2.0
class:text-black={bg_color} class:text-black={bg_color}
> >
<p class="mr-auto ml-0 col-start-1 col-end-1"> <p class="mr-auto ml-0 col-start-1 col-end-1">
{selected_question === -1 ? '0' : selected_question + 1} {game_state.selected_question === -1 ? '0' : game_state.selected_question + 1}
/{quiz_data.questions.length} /{game_state.quiz_data.questions.length}
</p> </p>
<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' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)} {#if game_state.selected_question + 1 === game_state.quiz_data.questions.length && ((game_state.timer_res === '0' && game_state.question_results !== null) || game_state.quiz_data?.questions?.[game_state.selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(final_results) === JSON.stringify([null])} {#if JSON.stringify(game_state.final_results) === JSON.stringify([null])}
<button onclick={() => socket_game_controls.get_final_results()} class="admin-button" <button onclick={() => socket_game_controls.get_final_results()} class="admin-button"
>{$t('admin_page.get_final_results')} >{$t('admin_page.get_final_results')}
</button> </button>
{/if} {/if}
{:else if timer_res === '0' || selected_question === -1} {:else if game_state.timer_res === '0' || game_state.selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} {#if (game_state.selected_question + 1 !== game_state.quiz_data.questions.length && game_state.question_results !== null) || game_state.selected_question === -1}
<button <button
onclick={() => { onclick={() => {
socket_game_controls.set_question_number(selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })}
</button> </button>
{/if} {/if}
{#if question_results === null && selected_question !== -1} {#if game_state.question_results === null && game_state.selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
<button <button
onclick={() => { onclick={() => {
socket_game_controls.set_question_number(selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })}
</button> </button>
{:else if quiz_data.questions[selected_question]?.hide_results === true} {:else if game_state.quiz_data.questions[game_state.selected_question]?.hide_results === true}
<button <button
onclick={() => { onclick={() => {
socket_game_controls.get_question_results(game_token, shown_question_now); socket_game_controls.get_question_results(game_token, game_state.shown_question_now);
setTimeout(() => { setTimeout(() => {
socket_game_controls.set_question_number(selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
}, 200); }, 200);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })}
</button> </button>
{:else} {:else}
<button onclick={() => socket_game_controls.get_question_results(game_token, shown_question_now)} class="admin-button" <button onclick={() => socket_game_controls.get_question_results(game_token, game_state.shown_question_now)} class="admin-button"
>{$t('admin_page.show_results')} >{$t('admin_page.show_results')}
</button> </button>
{/if} {/if}
{/if} {/if}
{:else if selected_question !== -1} {:else if game_state.selected_question !== -1}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE}
<button <button
onclick={() => { onclick={() => {
socket_game_controls.set_question_number(selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
}} }}
class="admin-button" class="admin-button"
>{$t('admin_page.next_question', { question: selected_question + 2 })} >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })}
</button> </button>
{:else} {:else}
<button onclick={show_solutions} class="admin-button" <button onclick={show_solutions} class="admin-button"
@@ -11,15 +11,16 @@ SPDX-License-Identifier: MPL-2.0
import GrayButton from '$lib/components/buttons/gray.svelte'; import GrayButton from '$lib/components/buttons/gray.svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts'; import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import { GameState } from '$lib/play/admin/game_state';
interface Props { interface Props {
game_pin: string; game_pin: string;
players: any; game_state: GameState;
socket_game_controls: SocketGameControls; socket_game_controls: SocketGameControls;
cqc_code: string; cqc_code: string;
} }
let { game_pin, players = $bindable(), socket_game_controls, cqc_code = $bindable() }: Props = $props(); let { game_pin, game_state = $bindable(), socket_game_controls, cqc_code = $bindable() }: Props = $props();
let fullscreen_open = $state(false); let fullscreen_open = $state(false);
const { t } = getLocalization(); const { t } = getLocalization();
@@ -55,13 +56,13 @@ SPDX-License-Identifier: MPL-2.0
<div class="m-auto"> <div class="m-auto">
<div class="flex justify-center my-4"> <div class="flex justify-center my-4">
<p class="m-auto text-2xl"> <p class="m-auto text-2xl">
{#if players.length <= 1} {#if game_state.players.length <= 1}
{$t('play_page.players_waiting', { {$t('play_page.players_waiting', {
count: players.length ?? 0 count: game_state.players.length ?? 0
})} })}
{:else} {:else}
{$t('play_page.players_waiting_plural', { {$t('play_page.players_waiting_plural', {
count: players.length ?? 0 count: game_state.players.length ?? 0
})} })}
{/if} {/if}
</p> </p>
@@ -74,13 +75,13 @@ SPDX-License-Identifier: MPL-2.0
{:else} {:else}
<div class="flex justify-center"> <div class="flex justify-center">
<p class="m-auto text-2xl"> <p class="m-auto text-2xl">
{#if players.length <= 1} {#if game_state.players.length <= 1}
{$t('play_page.players_waiting', { {$t('play_page.players_waiting', {
count: players.length ?? 0 count: game_state.players.length ?? 0
})} })}
{:else} {:else}
{$t('play_page.players_waiting_plural', { {$t('play_page.players_waiting_plural', {
count: players.length ?? 0 count: game_state.players.length ?? 0
})} })}
{/if} {/if}
</p> </p>
@@ -93,7 +94,7 @@ SPDX-License-Identifier: MPL-2.0
<div class="flex justify-center w-full mt-4"> <div class="flex justify-center w-full mt-4">
<div> <div>
<GrayButton <GrayButton
disabled={players.length < 1} disabled={game_state.players.length < 1}
onclick={() => { onclick={() => {
socket_game_controls.start_game() socket_game_controls.start_game()
}} }}
@@ -102,13 +103,13 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </div>
<div class="flex flex-row w-full mt-4 px-10 flex-wrap"> <div class="flex flex-row w-full mt-4 px-10 flex-wrap">
{#if players.length > 0} {#if game_state.players.length > 0}
{#each players as player} {#each game_state.players as player}
<div class="p-2 m-2 border-2 border-[#B07156] rounded-sm hover:cursor-pointer"> <div class="p-2 m-2 border-2 border-[#B07156] rounded-sm hover:cursor-pointer">
<span <span
class="hover:line-through text-lg" class="hover:line-through text-lg"
onclick={() => { onclick={() => {
socket_game_controls.kick_player(player.username, players); socket_game_controls.kick_player(player.username, game_state.players);
}}>{player.username}</span }}>{player.username}</span
> >
<!-- <button>{$t('words.kick')}</button>--> <!-- <button>{$t('words.kick')}</button>-->
+17 -21
View File
@@ -1,23 +1,19 @@
export class GameState { import type { Player, PlayerAnswer } from '$lib/admin.ts';
public game_id: string; import { QuizData } from '$lib/quiz_types';
public players: any[];
public selected_question: number;
public timer_res: string;
public question_results: any;
public answer_count: number;
public shown_question_now: number;
public final_results: any;
public game_started: boolean;
constructor(game_id: string, players: any[]) { export interface IGameState {
this.game_id = game_id; game_id: string;
this.players = players; players: Player[];
this.selected_question = -1; player_scores: Record<string, number>;
this.timer_res = '0'; selected_question: number;
this.question_results = null; timer_res: string;
this.answer_count = 0; question_results: any;
this.shown_question_now = -1; answer_count: number;
this.final_results = null; shown_question_now: number;
this.game_started = false; final_results: Array<null> | Array<Array<PlayerAnswer>>;
} game_started: boolean;
quiz_data: QuizData;
control_visible: boolean;
constructor(game_id: string);
} }
+58 -41
View File
@@ -5,12 +5,9 @@ SPDX-License-Identifier: MPL-2.0
--> -->
<script lang="ts"> <script lang="ts">
import type { QuizData } from '$lib/quiz_types';
import { socket } from '$lib/socket'; import { socket } from '$lib/socket';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores.svelte.ts'; import { navbarVisible } from '$lib/stores.svelte.ts';
import type { PlayerAnswer, Player } from '$lib/admin.ts';
import SomeAdminScreen from '$lib/admin.svelte'; import SomeAdminScreen from '$lib/admin.svelte';
import GameNotStarted from '$lib/play/admin/game_not_started.svelte'; import GameNotStarted from '$lib/play/admin/game_not_started.svelte';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
@@ -19,7 +16,7 @@ SPDX-License-Identifier: MPL-2.0
import GrayButton from '$lib/components/buttons/gray.svelte'; import GrayButton from '$lib/components/buttons/gray.svelte';
import { page } from '$app/state'; import { page } from '$app/state';
import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts'; import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts';
import { GameState } from '$lib/play/admin/game_state.ts'; import { IGameState } from '$lib/play/admin/game_state.ts';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
navbarVisible.visible = false; navbarVisible.visible = false;
@@ -35,25 +32,50 @@ SPDX-License-Identifier: MPL-2.0
data: any; data: any;
} }
class GameState implements IGameState {
public game_id: string;
public players: Player[];
public player_scores: Record<string, number>;
public selected_question: number;
public timer_res: string;
public question_results: any;
public answer_count: number;
public shown_question_now: number;
public final_results: Array<null> | Array<Array<PlayerAnswer>>;
public game_started: boolean;
public quiz_data: QuizData;
public control_visible: boolean;
constructor(game_id: string) {
this.game_id = game_id;
this.players = $state([]);
this.player_scores = $state({});
this.selected_question = $state(-1);
this.timer_res = $state(undefined);
this.quiz_data = $state(null);
this.control_visible = $state(true);
this.shown_question_now = $state(-1);
this.final_results = $state([null]);
this.game_started = $state(false);
this.question_results = $state(null);
this.answer_count = $state(0);
}
}
let { data }: Props = $props(); let { data }: Props = $props();
let game_mode = $state(); let game_mode = $state();
let { auto_connect, game_token } = $state(data); let { auto_connect, game_token } = $state(data);
const game_pin = data.game_pin; const game_pin = data.game_pin;
let players: Array<Player> = $state([]);
let player_scores = $state({});
let errorMessage = $state(''); let errorMessage = $state('');
let game_started = $state(false);
let quiz_data: QuizData = $state();
let control_visible = $state(true);
//let question_number = '0';
// let question_results = null;
let final_results: Array<null> | Array<Array<PlayerAnswer>> = $state([null]);
let success = $state(false); let success = $state(false);
let dataexport_download_a = $state(); let dataexport_download_a = $state();
let warnToLeave = true; let warnToLeave = true;
let export_token = $state(undefined); let export_token = $state(undefined);
const socket_game_controls: SocketGameControls = new SocketGameControls(socket);
let game_state: GameState = $state(new GameState(game_token));
const connect = async () => { const connect = async () => {
socket.emit('register_as_admin', { socket.emit('register_as_admin', {
game_pin: game_pin, game_pin: game_pin,
@@ -73,12 +95,12 @@ SPDX-License-Identifier: MPL-2.0
}); });
socket.on('registered_as_admin', (data) => { socket.on('registered_as_admin', (data) => {
quiz_data = JSON.parse(data['game']); game_state.quiz_data = JSON.parse(data['game']);
console.log(quiz_data); console.log(game_state.quiz_data);
success = true; success = true;
}); });
socket.on('player_joined', (int_data) => { socket.on('player_joined', (int_data) => {
players = [...players, int_data]; game_state.players = [...game_state.players, int_data];
}); });
socket.on('already_registered_as_admin', () => { socket.on('already_registered_as_admin', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -87,11 +109,11 @@ SPDX-License-Identifier: MPL-2.0
}); });
socket.on('start_game', (_) => { socket.on('start_game', (_) => {
game_started = true; game_state.game_started = true;
}); });
socket.on('control_visibility', (data) => { socket.on('control_visibility', (data) => {
control_visible = data.visible; game_state.control_visible = data.visible;
}); });
/* socket.on('question_results', (int_data) => { /* socket.on('question_results', (int_data) => {
@@ -141,40 +163,39 @@ SPDX-License-Identifier: MPL-2.0
window.matchMedia('(prefers-color-scheme: dark)').matches); window.matchMedia('(prefers-color-scheme: dark)').matches);
} }
let bg_color = $derived(quiz_data ? quiz_data.background_color : undefined); let bg_color = $derived(game_state.quiz_data ? game_state.quiz_data.background_color : undefined);
let bg_image = $derived(quiz_data ? quiz_data.background_image : undefined); let bg_image = $derived(game_state.quiz_data ? game_state.quiz_data.background_image : undefined);
let results_saved = $state(false); let results_saved = $state(false);
let show_final_results = $derived(JSON.stringify(final_results) !== JSON.stringify([null])); let show_final_results = $derived(JSON.stringify(game_state.final_results) !== JSON.stringify([null]));
const socket_game_controls: SocketGameControls = new SocketGameControls(socket);
let game_state: GameState = $state(new GameState(game_token));
// This function in called in every keyboard event in this page // This function in called in every keyboard event in this page
const next_action = (e: KeyboardEvent) => { const next_action = (e: KeyboardEvent) => {
if((e.key in ["Enter", " "])) return; // Don't catch events other than enter or spacebar if((e.key in ["Enter", " "])) return; // Don't catch events other than enter or spacebar
if(!game_started && players.length > 0) { // Game not started with conditions matched console.log(game_state);
if(!game_state.game_started && game_state.players.length > 0) { // Game not started with conditions matched
socket_game_controls.start_game() socket_game_controls.start_game()
} }
// This is a messy condition to check if we can show final results // This is a messy condition to check if we can show final results
else if(game_state.selected_question + 1 === quiz_data.questions.length && ((game_state.timer_res === '0' && game_state.question_results !== null) || quiz_data?.questions?.[game_state.selected_question]?.type === QuizQuestionType.SLIDE)){ else if(game_state.selected_question + 1 === game_state.quiz_data.questions.length && ((game_state.timer_res === '0' && game_state.question_results !== null) || game_state.quiz_data?.questions?.[game_state.selected_question]?.type === QuizQuestionType.SLIDE)){
socket_game_controls.get_final_results(); socket_game_controls.get_final_results();
} }
// Game is ongoing and the current question is eather finished or quiz just started // Game is ongoing and the current question is either finished or quiz just started
else if(game_state.timer_res === '0' || game_state.selected_question === -1) { else if(game_state.timer_res === '0' || game_state.selected_question === -1) {
// We are eather in the beginning of the quiz or in the end of it // We are either in the beginning of the quiz or in the end of it
if((game_state.selected_question + 1 !== quiz_data.questions.length && game_state.question_results !== null) || game_state.selected_question === -1) { if((game_state.selected_question + 1 !== game_state.quiz_data.questions.length && game_state.question_results !== null) || game_state.selected_question === -1) {
socket_game_controls.set_question_number(game_state.selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
} }
// Else we are in the middle of a question // Else we are in the middle of a question
else if(game_state.question_results === null && game_state.selected_question !== -1) { else if(game_state.question_results === null && game_state.selected_question !== -1) {
if(quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE) { if(game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE) {
socket_game_controls.set_question_number(game_state.selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
} }
else if(quiz_data.questions[game_state.selected_question]?.hide_results === true) { else if(game_state.quiz_data.questions[game_state.selected_question]?.hide_results === true) {
socket_game_controls.get_question_results(game_token, game_state.shown_question_now); socket_game_controls.get_question_results(game_token, game_state.shown_question_now);
setTimeout(() => { setTimeout(() => {
socket_game_controls.set_question_number(game_state.selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
@@ -188,7 +209,7 @@ SPDX-License-Identifier: MPL-2.0
// Game started, question not finished we check if the question is slide or not // Game started, question not finished we check if the question is slide or not
else if(game_state.selected_question !== -1) { else if(game_state.selected_question !== -1) {
switch(quiz_data.questions[game_state.selected_question].type) { switch(game_state.quiz_data.questions[game_state.selected_question].type) {
case QuizQuestionType.SLIDE: case QuizQuestionType.SLIDE:
socket_game_controls.set_question_number(game_state.selected_question + 1); socket_game_controls.set_question_number(game_state.selected_question + 1);
break; break;
@@ -213,8 +234,8 @@ SPDX-License-Identifier: MPL-2.0
: `unset`}; background-color: {bg_color ? bg_color : 'transparent'}" : `unset`}; background-color: {bg_color ? bg_color : 'transparent'}"
class:text-black={bg_color} class:text-black={bg_color}
> >
{#if JSON.stringify(final_results) !== JSON.stringify([null])} {#if JSON.stringify(game_state.final_results) !== JSON.stringify([null])}
{#if control_visible} {#if game_state.control_visible}
<div class="w-screen flex justify-center mt-16"> <div class="w-screen flex justify-center mt-16">
<div class="w-fit"> <div class="w-fit">
{#if export_token === undefined} {#if export_token === undefined}
@@ -254,27 +275,23 @@ SPDX-License-Identifier: MPL-2.0
</div> </div>
</div> </div>
{/if} {/if}
<FinalResults bind:data={player_scores} {show_final_results} /> <FinalResults bind:data={game_state.player_scores} {show_final_results} />
{/if} {/if}
{#if !success} {#if !success}
{#if errorMessage !== ''} {#if errorMessage !== ''}
<p class="text-red-700">{errorMessage}</p> <p class="text-red-700">{errorMessage}</p>
{/if} {/if}
{:else if !game_started} {:else if !game_state.game_started}
<GameNotStarted <GameNotStarted
{game_pin} {game_pin}
bind:players bind:game_state
{socket_game_controls} {socket_game_controls}
cqc_code={page.url.searchParams.get('cqc_code')} cqc_code={page.url.searchParams.get('cqc_code')}
/> />
{:else} {:else}
<SomeAdminScreen <SomeAdminScreen
bind:final_results
{game_token} {game_token}
bind:quiz_data
{bg_color} {bg_color}
bind:player_scores
{control_visible}
bind:game_state bind:game_state
/> />
{/if} {/if}