From 085fcdb63efbb80dcf619e020f41a4f0a1ccecbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20P=C4=99dzich?= Date: Thu, 8 Jun 2023 18:58:14 +0200 Subject: [PATCH] Create and use the useVote composable --- src/composables/useVote.ts | 122 +++++++++++++++++++++++++++++++++++++ src/views/Vote.vue | 27 +++----- 2 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 src/composables/useVote.ts diff --git a/src/composables/useVote.ts b/src/composables/useVote.ts new file mode 100644 index 0000000..f95f8ef --- /dev/null +++ b/src/composables/useVote.ts @@ -0,0 +1,122 @@ +import { computed, reactive, ref, toRefs } from 'vue'; +import glicko2 from 'glicko2-lite'; + +import photos from '@/photos.json'; +import { Database, Photo } from '@/models'; +import { randomNumber } from '@/utils/randomNumber'; + +const db = reactive( + JSON.parse(localStorage.getItem('db') as string) || { + photos, + votes: [] + } +); + +const photosInCurrentVote = ref([]); + +const photosForFirstPick = computed(() => + db.photos.filter(({ fileName }) => { + const appearanceCount = db.votes.filter((vote) => + vote.photos.includes(fileName) + ).length; + + return appearanceCount !== db.photos.length - 1; + }) +); + +export function useVote() { + const pickPhotosForNewVote = () => { + const firstPick = + photosForFirstPick.value[ + randomNumber(0, photosForFirstPick.value.length - 1) + ]; + + const photosForSecondPick = db.photos.filter(({ fileName }) => { + const votesWithFirstPick = db.votes.filter(({ photos }) => + photos.includes(firstPick.fileName) + ); + + const fileNamesToExclude = [ + ...new Set(votesWithFirstPick.flatMap(({ photos }) => photos)) + ]; + + return !fileNamesToExclude.includes(fileName); + }); + + const secondPick = + photosForSecondPick[randomNumber(0, photosForSecondPick.length - 1)]; + + photosInCurrentVote.value.push(firstPick, secondPick); + }; + + const submitVote = (result: 0 | 0.5 | 1) => { + const fileNames = [...photosInCurrentVote.value].map( + ({ fileName }) => fileName + ) as [string, string]; + + db.votes.unshift({ photos: fileNames, result }); + photosInCurrentVote.value = []; + pickPhotosForNewVote(); + }; + + const updateRatings = () => { + const twelveMostRecentVotes = db.votes.slice(0, 12).reverse(); + + const votesGrouppedByPhotos = twelveMostRecentVotes.reduce((obj, vote) => { + const [firstPick, secondPick] = vote.photos; + + const firstPhoto = db.photos.find( + ({ fileName }) => fileName === firstPick + )!; + const secondPhoto = db.photos.find( + ({ fileName }) => fileName === secondPick + )!; + + const firstPhotoOpponentParams = [ + secondPhoto.rating, + secondPhoto.rd, + vote.result + ] as [number, number, number]; + + const secondPhotoOpponentParams = [ + firstPhoto.rating, + firstPhoto.rd, + 1 - vote.result + ] as [number, number, number]; + + if (obj[firstPick]) { + obj[firstPick].push(firstPhotoOpponentParams); + } else { + obj[firstPick] = [firstPhotoOpponentParams]; + } + + if (obj[secondPick]) { + obj[secondPick].push(secondPhotoOpponentParams); + } else { + obj[secondPick] = [secondPhotoOpponentParams]; + } + + return obj; + }, {} as Record); + + for (const [photoFileName, votes] of Object.entries( + votesGrouppedByPhotos + )) { + const photo = db.photos.find( + ({ fileName }) => fileName === photoFileName + )!; + const newRatingParams = glicko2(photo.rating, photo.rd, photo.vol, votes); + + Object.assign(photo, newRatingParams); + } + }; + + return { + photosInCurrentVote, + photosForFirstPick, + createVote: pickPhotosForNewVote, + submitVote, + updateRatings, + ...toRefs(db) + }; +} diff --git a/src/views/Vote.vue b/src/views/Vote.vue index 1573177..1eb2588 100644 --- a/src/views/Vote.vue +++ b/src/views/Vote.vue @@ -1,18 +1,7 @@