diff --git a/common/GameState.ts b/common/GameState.ts index eee24b7..073ae12 100644 --- a/common/GameState.ts +++ b/common/GameState.ts @@ -5,7 +5,7 @@ export type GameState = BuzzerState | QuizState | StopwatchState; export type StopwatchState = | StopwatchPreparationState | StopwatchRunningState - | StopwatchDoneState; + | StopwatchCompletedState; export type StopwatchPreparationState = { game: 'stopwatch'; @@ -16,20 +16,21 @@ export type StopwatchRunningState = { game: 'stopwatch'; name: 'running'; time: number; - pressedControllers: Map; + result: Record; }; -export type StopwatchDoneState = { +export type StopwatchCompletedState = { game: 'stopwatch'; name: 'completed'; time: number; - pressedControllers: Map; + result: Record; }; export type BuzzerState = | BuzzerPreparationState | BuzzerRunningState | BuzzerAnsweringState; + export type BuzzerPreparationState = { game: 'buzzer'; name: 'preparing'; diff --git a/src/components/questions/stopwatch/StopwatchEntry.ts b/src/components/questions/stopwatch/StopwatchEntry.ts new file mode 100644 index 0000000..c6d58b3 --- /dev/null +++ b/src/components/questions/stopwatch/StopwatchEntry.ts @@ -0,0 +1,6 @@ +import { IController } from 'src/plugins/buzzer/types'; + +export type StopwatchEntry = { + controller: IController; + time: number | undefined; +}; diff --git a/src/components/questions/stopwatch/StopwatchScoreDialog.vue b/src/components/questions/stopwatch/StopwatchScoreDialog.vue index e0b059b..6a19133 100644 --- a/src/components/questions/stopwatch/StopwatchScoreDialog.vue +++ b/src/components/questions/stopwatch/StopwatchScoreDialog.vue @@ -14,8 +14,8 @@ - {{ index + 1 }} + + - {{ controller.name }} + {{ entry.controller.name }} import { useDialogPluginComponent } from 'quasar'; import { useI18n } from 'vue-i18n'; -import { IController } from 'src/plugins/buzzer/types'; import { ref, toRaw } from 'vue'; +import { StopwatchEntry } from 'components/questions/stopwatch/StopwatchEntry'; const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent(); const { t } = useI18n(); const props = defineProps<{ - controllers: IController[]; + result: StopwatchEntry[]; scores: Record; }>(); @@ -81,11 +84,11 @@ defineEmits([...useDialogPluginComponent.emits]); const avatarColor = (index: number) => { switch (index) { - case 1: + case 0: return 'primary'; - case 2: + case 1: return 'secondary'; - case 3: + case 2: return 'info'; } diff --git a/src/components/questions/stopwatch/StopwatchScoreboardButton.vue b/src/components/questions/stopwatch/StopwatchScoreboardButton.vue index 1661256..14320ef 100644 --- a/src/components/questions/stopwatch/StopwatchScoreboardButton.vue +++ b/src/components/questions/stopwatch/StopwatchScoreboardButton.vue @@ -13,7 +13,7 @@ import StopwatchScoreDialog from 'components/questions/stopwatch/StopwatchScoreDialog.vue'; import { useQuasar } from 'quasar'; import { useScoreboardStore } from 'stores/scoreboard-store'; -import { IController } from 'src/plugins/buzzer/types'; +import { StopwatchEntry } from 'components/questions/stopwatch/StopwatchEntry'; const quasar = useQuasar(); const scoreboardStore = useScoreboardStore(); @@ -22,17 +22,15 @@ let scores: Record = {}; const props = defineProps<{ label: string | undefined; - controllers: IController[]; + result: StopwatchEntry[]; }>(); const updateScores = () => { - const controllers = props.controllers; - quasar .dialog({ component: StopwatchScoreDialog, componentProps: { - controllers, + result: props.result, scores, }, }) diff --git a/src/pages/questions/StopwatchQuestionPage.vue b/src/pages/questions/StopwatchQuestionPage.vue index 586979b..4fde211 100644 --- a/src/pages/questions/StopwatchQuestionPage.vue +++ b/src/pages/questions/StopwatchQuestionPage.vue @@ -24,17 +24,17 @@
- {{ item[0].name }} + {{ item.controller.name }} - {{ formatTime(item[1]) }} + {{ formatTime(item.time) }} - + @@ -179,6 +179,7 @@ import StopwatchScoreboardButton from 'components/questions/stopwatch/StopwatchS import { useQuestionSettingsStore } from 'stores/question-settings-store'; import { useQuasar } from 'quasar'; import { StopwatchState } from 'app/common/GameState'; +import { StopwatchEntry } from 'components/questions/stopwatch/StopwatchEntry'; const { t } = useI18n(); const quasar = useQuasar(); @@ -209,6 +210,42 @@ const soundsEnabled = computed(() => { return stopwatchSettings.playSounds; }); +const result = computed(() => { + const state = gameState.value; + if (state.name !== 'running' && state.name !== 'completed') { + return []; + } + + return controllers.value + .filter((controller) => { + return state.name === 'completed' || controller.id in state.result; + }) + .map((controller): StopwatchEntry => { + const time = + controller.id in state.result ? state.result[controller.id] : undefined; + + return { + controller, + time, + }; + }) + .sort((a, b) => { + if (a.time === undefined && b.time === undefined) { + return 0; + } + + if (a.time === undefined) { + return 1; + } + + if (b.time === undefined) { + return -1; + } + + return a.time - b.time; + }); +}); + const listener = (event: ButtonEvent) => { if (gameState.value.name !== 'running') { return; @@ -218,18 +255,26 @@ const listener = (event: ButtonEvent) => { return; } - if (gameState.value.pressedControllers.has(event.controller)) { + if (event.controller.id in gameState.value.result) { return; } // Calculate exact time so that timer interval does not affect precision const time = new Date().getTime() - startTime.value; - // TODO Is a state change needed as transition here? - gameState.value.pressedControllers.set(event.controller, time); + gameState.value = { + game: 'stopwatch', + name: 'running', + time: gameState.value.time, + result: { + ...gameState.value.result, + [event.controller.id]: time, + }, + }; + event.controller.setLight(true); - if (gameState.value.pressedControllers.size === controllers.value.length) { + if (Object.keys(gameState.value.result).length === controllers.value.length) { onComplete(); } @@ -248,7 +293,7 @@ const onComplete = () => { game: 'stopwatch', name: 'completed', time: gameState.value.time, - pressedControllers: gameState.value.pressedControllers, + result: gameState.value.result, }; }; @@ -263,20 +308,20 @@ const stop = () => { return; } - const pressedControllers = gameState.value.pressedControllers; + const result: Record = gameState.value.result; for (const controller of controllers.value) { - if (pressedControllers.has(controller)) { + if (controller.id in result) { continue; } - pressedControllers.set(controller, Number.POSITIVE_INFINITY); + result[controller.id] = undefined; } gameState.value = { game: 'stopwatch', name: 'completed', time: gameState.value.time, - pressedControllers, + result, }; }; @@ -299,14 +344,14 @@ const start = () => { game: 'stopwatch', name: 'running', time: 0, - pressedControllers: new Map(), + result: {}, }; startTime.value = new Date().getTime(); }; -const formatTime = (time: number) => { - if (!Number.isFinite(time)) { +const formatTime = (time: number | undefined) => { + if (time === undefined) { return t('question.stopwatch.disqualified'); } @@ -323,15 +368,14 @@ const formatTime = (time: number) => { const removeController = (controller: IController) => { if (gameState.value.name === 'completed') { - const pressedControllers = gameState.value.pressedControllers; - pressedControllers.delete(controller); - pressedControllers.set(controller, Number.POSITIVE_INFINITY); - gameState.value = { game: 'stopwatch', name: 'completed', time: gameState.value.time, - pressedControllers, + result: { + ...gameState.value.result, + [controller.id]: undefined, + }, }; controller.setLight(false); @@ -340,14 +384,14 @@ const removeController = (controller: IController) => { } if (gameState.value.name === 'running') { - const pressedControllers = gameState.value.pressedControllers; - pressedControllers.delete(controller); + const result = { ...gameState.value.result }; + delete result[controller.id]; gameState.value = { game: 'stopwatch', - name: 'completed', + name: 'running', time: gameState.value.time, - pressedControllers, + result, }; controller.setLight(false);