Fix duplicate picks and recursive rating updates

This commit is contained in:
Maciej Pędzich 2023-06-09 12:57:53 +02:00
parent 053e4e0302
commit 5cc46fbd6b
2 changed files with 86 additions and 76 deletions

View File

@ -1,13 +1,13 @@
import { computed, reactive, ref, toRefs } from 'vue'; import { computed, reactive, ref, toRefs, watchEffect } from 'vue';
import glicko2 from 'glicko2-lite'; import glicko2 from 'glicko2-lite';
import photos from '@/photos.json'; import defaultPhotos from '@/photos.json';
import { Database, Photo } from '@/models'; import { Database, Photo } from '@/models';
import { randomNumber } from '@/utils/randomNumber'; import { randomNumber } from '@/utils/randomNumber';
const db = reactive<Database>( const db = reactive<Database>(
JSON.parse(localStorage.getItem('db') as string) || { JSON.parse(localStorage.getItem('db') as string) || {
photos, photos: defaultPhotos,
votes: [] votes: []
} }
); );
@ -24,93 +24,100 @@ const photosForFirstPick = computed(() =>
}) })
); );
export function useVote() { const pickPhotosForNewVote = () => {
const pickPhotosForNewVote = () => { const firstPick =
const firstPick = photosForFirstPick.value[
photosForFirstPick.value[ randomNumber(0, photosForFirstPick.value.length - 1)
randomNumber(0, photosForFirstPick.value.length - 1) ];
];
const photosForSecondPick = db.photos.filter(({ fileName }) => { const photosForSecondPick = db.photos.filter(({ fileName }) => {
const votesWithFirstPick = db.votes.filter(({ photos }) => const votesWithFirstPick = db.votes.filter(({ photos }) =>
photos.includes(firstPick.fileName) photos.includes(firstPick.fileName)
); );
const fileNamesToExclude = [ const fileNamesToExclude = [
...new Set(votesWithFirstPick.flatMap(({ photos }) => photos)) ...new Set(
]; votesWithFirstPick
.flatMap(({ photos }) => photos)
.concat([firstPick.fileName])
)
];
return !fileNamesToExclude.includes(fileName); return !fileNamesToExclude.includes(fileName);
}); });
const secondPick = const secondPick =
photosForSecondPick[randomNumber(0, photosForSecondPick.length - 1)]; photosForSecondPick[randomNumber(0, photosForSecondPick.length - 1)];
photosInCurrentVote.value.push(firstPick, secondPick); photosInCurrentVote.value = [firstPick, secondPick];
}; };
const submitVote = (result: 0 | 0.5 | 1) => { const submitVote = (result: 0 | 0.5 | 1) => {
const fileNames = [...photosInCurrentVote.value].map( const fileNames = ([...photosInCurrentVote.value] as Photo[]).map(
({ fileName }) => fileName ({ fileName }) => fileName
) as [string, string]; ) as [string, string];
db.votes.unshift({ photos: fileNames, result }); db.votes.unshift({ photos: fileNames, result });
photosInCurrentVote.value = [];
pickPhotosForNewVote();
};
const updateRatings = () => { if (db.votes.length % 12 === 0) {
const twelveMostRecentVotes = db.votes.slice(0, 12).reverse(); updateRatings();
}
const votesGrouppedByPhotos = twelveMostRecentVotes.reduce((obj, vote) => { pickPhotosForNewVote();
const [firstPick, secondPick] = vote.photos; };
const firstPhoto = db.photos.find( const updateRatings = () => {
({ fileName }) => fileName === firstPick const twelveMostRecentVotes = db.votes.slice(0, 12).reverse();
)!;
const secondPhoto = db.photos.find(
({ fileName }) => fileName === secondPick
)!;
const firstPhotoOpponentParams = [ const votesGrouppedByPhotos = twelveMostRecentVotes.reduce((obj, vote) => {
secondPhoto.rating, const [firstFileName, secondFileName] = vote.photos;
secondPhoto.rd,
vote.result
] as [number, number, number];
const secondPhotoOpponentParams = [ const firstPhoto = db.photos.find(
firstPhoto.rating, ({ fileName }) => fileName === firstFileName
firstPhoto.rd, )!;
1 - vote.result
] as [number, number, number];
if (obj[firstPick]) { const secondPhoto = db.photos.find(
obj[firstPick].push(firstPhotoOpponentParams); ({ fileName }) => fileName === secondFileName
} else { )!;
obj[firstPick] = [firstPhotoOpponentParams];
}
if (obj[secondPick]) { const firstPhotoOpponentParams = [
obj[secondPick].push(secondPhotoOpponentParams); secondPhoto.rating,
} else { secondPhoto.rd,
obj[secondPick] = [secondPhotoOpponentParams]; vote.result
} ] as [number, number, number];
return obj; const secondPhotoOpponentParams = [
}, {} as Record<string, [number, number, number][]>); firstPhoto.rating,
firstPhoto.rd,
1 - vote.result
] as [number, number, number];
for (const [photoFileName, votes] of Object.entries( if (obj[firstFileName]) {
votesGrouppedByPhotos obj[firstFileName].push(firstPhotoOpponentParams);
)) { } else {
const photo = db.photos.find( obj[firstFileName] = [firstPhotoOpponentParams];
({ fileName }) => fileName === photoFileName
)!;
const newRatingParams = glicko2(photo.rating, photo.rd, photo.vol, votes);
Object.assign(photo, newRatingParams);
} }
};
if (obj[secondFileName]) {
obj[secondFileName].push(secondPhotoOpponentParams);
} else {
obj[secondFileName] = [secondPhotoOpponentParams];
}
return obj;
}, {} as Record<string, [number, number, number][]>);
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);
}
};
watchEffect(() => localStorage.setItem('db', JSON.stringify(db)));
export function useVote() {
return { return {
photosInCurrentVote, photosInCurrentVote,
photosForFirstPick, photosForFirstPick,

File diff suppressed because one or more lines are too long