Skip to content

Commit

Permalink
Merge pull request #66 from marvin-wtt/game-state
Browse files Browse the repository at this point in the history
Refactor state map to record
  • Loading branch information
marvin-wtt committed Apr 10, 2024
2 parents a362f90 + c1f23b8 commit f36658e
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 50 deletions.
9 changes: 5 additions & 4 deletions common/GameState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type GameState = BuzzerState | QuizState | StopwatchState;
export type StopwatchState =
| StopwatchPreparationState
| StopwatchRunningState
| StopwatchDoneState;
| StopwatchCompletedState;

export type StopwatchPreparationState = {
game: 'stopwatch';
Expand All @@ -16,20 +16,21 @@ export type StopwatchRunningState = {
game: 'stopwatch';
name: 'running';
time: number;
pressedControllers: Map<IController, number>;
result: Record<string, number>;
};

export type StopwatchDoneState = {
export type StopwatchCompletedState = {
game: 'stopwatch';
name: 'completed';
time: number;
pressedControllers: Map<IController, number>;
result: Record<string, number | undefined>;
};

export type BuzzerState =
| BuzzerPreparationState
| BuzzerRunningState
| BuzzerAnsweringState;

export type BuzzerPreparationState = {
game: 'buzzer';
name: 'preparing';
Expand Down
6 changes: 6 additions & 0 deletions src/components/questions/stopwatch/StopwatchEntry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IController } from 'src/plugins/buzzer/types';

export type StopwatchEntry = {
controller: IController;
time: number | undefined;
};
23 changes: 13 additions & 10 deletions src/components/questions/stopwatch/StopwatchScoreDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,29 @@
<q-card-section>
<q-list>
<q-item
v-for="(controller, index) in props.controllers"
:key="controller.id"
v-for="(entry, index) in result"
:key="entry.controller.id"
>
<q-item-section avatar>
<q-avatar
:color="avatarColor(index)"
text-color="white"
size="sm"
>
{{ index + 1 }}
<template v-if="entry.time !== undefined">
{{ index + 1 }}
</template>
<template v-else> - </template>
</q-avatar>
</q-item-section>

<q-item-section>
{{ controller.name }}
{{ entry.controller.name }}
</q-item-section>

<q-item-section side>
<q-input
v-model.number="updatedScores[controller.id]"
v-model.number="updatedScores[entry.controller.id]"
:label="t('question.stopwatch.scores.field')"
type="number"
outlined
Expand Down Expand Up @@ -62,14 +65,14 @@
<script lang="ts" setup>
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<string, number | undefined>;
}>();
Expand All @@ -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';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -22,17 +22,15 @@ let scores: Record<string, number | undefined> = {};
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,
},
})
Expand Down
106 changes: 75 additions & 31 deletions src/pages/questions/StopwatchQuestionPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@

<div class="col-grow relative-position">
<q-virtual-scroll
:items="Array.from(gameState.pressedControllers)"
:items="result"
class="absolute fit"
v-slot="{ item, index }"
v-slot="{ item, index }: { item: StopwatchEntry; index: number }"
>
<q-item
:key="item[0].id"
:key="item.controller.id"
dense
>
<q-item-section avatar>
<q-avatar
v-if="Number.isFinite(item[1])"
v-if="item.time !== undefined"
:color="avatarColor(index)"
text-color="white"
size="sm"
Expand All @@ -52,22 +52,22 @@
</q-item-section>

<q-item-section>
{{ item[0].name }}
{{ item.controller.name }}
</q-item-section>

<q-item-section side>
{{ formatTime(item[1]) }}
{{ formatTime(item.time) }}
</q-item-section>

<q-item-section side>
<!-- Disqualified buttons are infinite -->
<!-- Disqualified buttons are undefined -->
<safe-delete-btn
v-if="Number.isFinite(item[1])"
v-if="item.time !== undefined"
icon="close"
size="sm"
rounded
dense
@click="removeController(item[0])"
@click="removeController(item.controller)"
/>

<q-btn
Expand Down Expand Up @@ -135,7 +135,7 @@
<template v-else-if="gameState.name === 'completed'">
<stopwatch-scoreboard-button
:label="t('question.stopwatch.action.scores')"
:controllers="Array.from(gameState.pressedControllers.keys())"
:result="result"
/>

<q-separator />
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -209,6 +210,42 @@ const soundsEnabled = computed<boolean>(() => {
return stopwatchSettings.playSounds;
});
const result = computed<StopwatchEntry[]>(() => {
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;
Expand All @@ -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();
}
Expand All @@ -248,7 +293,7 @@ const onComplete = () => {
game: 'stopwatch',
name: 'completed',
time: gameState.value.time,
pressedControllers: gameState.value.pressedControllers,
result: gameState.value.result,
};
};
Expand All @@ -263,20 +308,20 @@ const stop = () => {
return;
}
const pressedControllers = gameState.value.pressedControllers;
const result: Record<string, number | undefined> = 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,
};
};
Expand All @@ -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');
}
Expand All @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit f36658e

Please sign in to comment.