From 4ed0b8d626baef7b23b027dc71442b3e23837a6b Mon Sep 17 00:00:00 2001 From: Proxyfil Date: Wed, 22 Apr 2026 20:59:24 +0200 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=9A=A7=20Created=20independant=20ga?= =?UTF-8?q?me=5Fcontrol=20and=20game=5Fstate=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/play/admin/game_state.ts | 21 ++++++++ .../lib/play/admin/socket_game_controls.ts | 50 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 frontend/src/lib/play/admin/game_state.ts create mode 100644 frontend/src/lib/play/admin/socket_game_controls.ts diff --git a/frontend/src/lib/play/admin/game_state.ts b/frontend/src/lib/play/admin/game_state.ts new file mode 100644 index 0000000..ba03b7e --- /dev/null +++ b/frontend/src/lib/play/admin/game_state.ts @@ -0,0 +1,21 @@ +export class GameState { + public game_id: string; + 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; + + constructor(game_id: string, players: any[]) { + this.game_id = game_id; + this.players = players; + this.selected_question = -1; + this.timer_res = '0'; + this.question_results = null; + this.answer_count = 0; + this.shown_question_now = -1; + this.final_results = null; + } +} \ No newline at end of file diff --git a/frontend/src/lib/play/admin/socket_game_controls.ts b/frontend/src/lib/play/admin/socket_game_controls.ts new file mode 100644 index 0000000..a50269c --- /dev/null +++ b/frontend/src/lib/play/admin/socket_game_controls.ts @@ -0,0 +1,50 @@ + +import type { Socket } from 'socket.io-client'; + +/** + * Class that encapsulates all the socket calls related to game controls for the admin page. + * Still in development, but it will be used to avoid having socket calls directly in the svelte files, and to have a single place to manage all the game controls related socket calls. + */ +export class SocketGameControls { + socket: Socket; + + constructor(socket: Socket) { + this.socket = socket; + } + + set_question_number(q_number: number) { + this.socket.emit('set_question_number', q_number.toString()); + }; + + get_question_results(game_id: string, question_number: number) { + this.socket.emit('get_question_results', { + game_id, + question_number + }); + }; + + show_solutions() { + this.socket.emit('show_solutions', {}); + }; + + get_final_results() { + this.socket.emit('get_final_results', {}); + }; + + start_game() { + this.socket.emit('start_game', ''); + } + + kick_player(username: string, players: any[]) { + this.socket.emit('kick_player', { username: username }); + + for (let i = 0; i < players.length; i++) { + console.log(players[i].username, username); + if (players[i].username === username) { + players.splice(i, 1); + break; + } + } + return players; + } +} \ No newline at end of file From 7629db7718d5892e5f248a89d50cdc1c5e5d9fe5 Mon Sep 17 00:00:00 2001 From: Proxyfil Date: Wed, 22 Apr 2026 21:02:00 +0200 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=9A=A7=20Added=20GameState=20and=20?= =?UTF-8?q?GameControl=20to=20quizzes=20to=20allow=20keyboard=20control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/admin.svelte | 29 ++++++++- frontend/src/lib/play/admin/controls.svelte | 40 +++++------- .../lib/play/admin/game_not_started.svelte | 21 ++---- frontend/src/routes/admin/+page.svelte | 64 ++++++++++++++++++- 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/frontend/src/lib/admin.svelte b/frontend/src/lib/admin.svelte index 7ee5f1c..76da064 100644 --- a/frontend/src/lib/admin.svelte +++ b/frontend/src/lib/admin.svelte @@ -14,6 +14,8 @@ SPDX-License-Identifier: MPL-2.0 import Spinner from '$lib/Spinner.svelte'; import Controls from '$lib/play/admin/controls.svelte'; import Question from '$lib/play/admin/question.svelte'; + import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts'; + import { GameState } from '$lib/play/admin/game_state.ts'; const { t } = getLocalization(); const default_colors = ['#D6EDC9', '#B07156', '#7F7057', '#4E6E58']; @@ -33,6 +35,7 @@ SPDX-License-Identifier: MPL-2.0 final_results?: Array | Array>; control_visible: boolean; player_scores: any; + game_state: GameState; } let { @@ -41,7 +44,8 @@ SPDX-License-Identifier: MPL-2.0 bg_color, final_results = $bindable([null]), control_visible, - player_scores = $bindable() + player_scores = $bindable(), + game_state = $bindable() }: Props = $props(); socket.on('get_question_results', () => { @@ -56,11 +60,20 @@ SPDX-License-Identifier: MPL-2.0 selected_question = selected_question + 1; answer_count = 0; timer(timer_res); + + game_state.timer_res = '0'; + game_state.question_results = null; + game_state.shown_question_now = data.question_index; + game_state.timer_res = quiz_data.questions[data.question_index].time; + game_state.selected_question = game_state.selected_question + 1; + game_state.answer_count = 0; }); socket.on('solutions', (_) => { timer_res = '0'; clearInterval(timer_interval); + + game_state.timer_res = '0'; }); socket.on('final_results', (data) => { @@ -68,20 +81,28 @@ SPDX-License-Identifier: MPL-2.0 final_results_clicked = true; timer_res = '0'; final_results = data; + + game_state.final_results = data; + game_state.timer_res = '0'; }); socket.on('everyone_answered', (_) => { timer_res = '0'; + game_state.timer_res = '0'; }); socket.on('question_results', (data) => { console.log('question_results:', data); question_results = data; timer_res = '0'; + + game_state.question_results = data; + game_state.timer_res = '0'; }); socket.on('player_answer', (_) => { answer_count += 1; + game_state.answer_count += 1; }); const timer = (time: string) => { @@ -95,8 +116,11 @@ SPDX-License-Identifier: MPL-2.0 } timer_res = seconds.toString(); + game_state.timer_res = seconds.toString(); }, 1000); }; + + const socket_game_controls: SocketGameControls = new SocketGameControls(socket); {#if control_visible} @@ -106,10 +130,11 @@ SPDX-License-Identifier: MPL-2.0 {quiz_data} bind:timer_res {final_results} - {socket} + {socket_game_controls} {question_results} {game_token} {shown_question_now} + bind:game_state /> {/if} {#if timer_res !== '0' && selected_question >= 0} diff --git a/frontend/src/lib/play/admin/controls.svelte b/frontend/src/lib/play/admin/controls.svelte index dcf44c7..1346870 100644 --- a/frontend/src/lib/play/admin/controls.svelte +++ b/frontend/src/lib/play/admin/controls.svelte @@ -9,6 +9,8 @@ SPDX-License-Identifier: MPL-2.0 import type { QuizData } from '$lib/quiz_types'; import type { Socket } from 'socket.io-client'; import { getLocalization } from '$lib/i18n'; + import { SocketGameControls } from '$lib/play/admin/socket_game_controls.ts'; + import { GameState } from '$lib/play/admin/game_state.ts'; interface Props { bg_color: string; @@ -16,10 +18,11 @@ SPDX-License-Identifier: MPL-2.0 quiz_data: QuizData; timer_res: string; final_results: any; - socket: Socket; + socket_game_controls: SocketGameControls; game_token: string; question_results: any; shown_question_now: number; + game_state: GameState; } let { @@ -28,30 +31,19 @@ SPDX-License-Identifier: MPL-2.0 quiz_data, timer_res = $bindable(), final_results, - socket, + socket_game_controls, game_token, question_results, - shown_question_now + shown_question_now, + game_state = $bindable() }: Props = $props(); const { t } = getLocalization(); - const set_question_number = (q_number: number) => { - socket.emit('set_question_number', q_number.toString()); - }; - const get_question_results = () => { - socket.emit('get_question_results', { - game_id: game_token, - question_number: shown_question_now - }); - }; const show_solutions = () => { - socket.emit('show_solutions', {}); + socket_game_controls.show_solutions(); timer_res = '0'; - }; - - const get_final_results = () => { - socket.emit('get_final_results', {}); + game_state.timer_res = '0'; }; @@ -67,7 +59,7 @@ SPDX-License-Identifier: MPL-2.0
{#if selected_question + 1 === quiz_data.questions.length && ((timer_res === '0' && question_results !== null) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)} {#if JSON.stringify(final_results) === JSON.stringify([null])} - {/if} @@ -75,7 +67,7 @@ SPDX-License-Identifier: MPL-2.0 {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} {:else} - {/if} @@ -111,7 +103,7 @@ SPDX-License-Identifier: MPL-2.0 {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} {/if} - {:else if timer_res === '0' || selected_question === -1} - {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} + {:else if game_state.timer_res === '0' || 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} {/if} - {#if question_results === null && selected_question !== -1} - {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} + {#if game_state.question_results === null && game_state.selected_question !== -1} + {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE} - {:else if quiz_data.questions[selected_question]?.hide_results === true} + {:else if game_state.quiz_data.questions[game_state.selected_question]?.hide_results === true} {:else} - {/if} {/if} - {:else if selected_question !== -1} - {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE} + {:else if game_state.selected_question !== -1} + {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE} {:else} {/if} @@ -55,31 +52,50 @@ SPDX-License-Identifier: MPL-2.0 socket_game_controls.set_question_number(game_state.selected_question + 1); }} class="admin-button" - >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })} + >{$t('admin_page.next_question', { + question: game_state.selected_question + 2 + })} {/if} {#if game_state.question_results === null && game_state.selected_question !== -1} {#if game_state.quiz_data.questions[game_state.selected_question].type === QuizQuestionType.SLIDE} {:else if game_state.quiz_data.questions[game_state.selected_question]?.hide_results === true} {:else} - {/if} @@ -91,7 +107,9 @@ SPDX-License-Identifier: MPL-2.0 socket_game_controls.set_question_number(game_state.selected_question + 1); }} class="admin-button" - >{$t('admin_page.next_question', { question: game_state.selected_question + 2 })} + >{$t('admin_page.next_question', { + question: game_state.selected_question + 2 + })} {:else}
Date: Tue, 28 Apr 2026 21:52:44 +0200 Subject: [PATCH 10/10] move to tinikeys and not allow start by keyboard --- frontend/src/routes/admin/+page.svelte | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte index 59d8c16..5a9f2aa 100644 --- a/frontend/src/routes/admin/+page.svelte +++ b/frontend/src/routes/admin/+page.svelte @@ -19,6 +19,7 @@ SPDX-License-Identifier: MPL-2.0 import type { IGameState } from '$lib/play/admin/game_state.ts'; import { QuizQuestionType, type QuizData } from '$lib/quiz_types'; import type { Player, PlayerAnswer } from '$lib/admin'; + import { tinykeys } from '$lib/tinykeys'; navbarVisible.visible = false; @@ -123,6 +124,10 @@ SPDX-License-Identifier: MPL-2.0 if (auto_connect) { connect(); } + tinykeys(window, { + Enter: next_action, + Space: next_action + }); }); socket.on('session_id', (d) => { const session_id = d.session_id; @@ -210,12 +215,8 @@ SPDX-License-Identifier: MPL-2.0 ); // This function in called in every keyboard event in this page - const next_action = (e: KeyboardEvent) => { - if (e.key in ['Enter', ' ']) return; // Don't catch events other than enter or spacebar - - if (game_state.is_game_ready_to_start()) { - socket_game_controls.start_game(); - } else if ( + const next_action = () => { + if ( game_state.is_active_question_last_question() && (game_state.is_question_results_visible() || game_state.is_active_question_slide()) ) { @@ -237,7 +238,7 @@ SPDX-License-Identifier: MPL-2.0 }; - + ClassQuiz - Host