sunrise-sunset: Update Streak Calculator if a Streak has just ended
This commit is contained in:
parent
89df3538b4
commit
daf73c797e
6 changed files with 109 additions and 90 deletions
5
src/routes/sunrise-sunset/GuessType.ts
Normal file
5
src/routes/sunrise-sunset/GuessType.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum GuessType {
|
||||||
|
correct = 'correct',
|
||||||
|
incorrect = 'incorrect',
|
||||||
|
missing = 'missing',
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
export let currentStreakLength: number;
|
export let currentStreakLength: number;
|
||||||
|
|
||||||
const todayAsString = formatDate(new Date(), "yyyy-MM-dd");
|
const todayAsString = formatDate(new Date(), "yyyy-MM-dd");
|
||||||
$: totalGuessCount = incorrectGuessDays.length + correctGuessDays.length;
|
|
||||||
const calculator = new SunriseSunsetStreakCalculator(todayAsString);
|
const calculator = new SunriseSunsetStreakCalculator(todayAsString);
|
||||||
let hasTextBeenCopied = false;
|
let hasTextBeenCopied = false;
|
||||||
|
|
||||||
|
|
|
||||||
23
src/routes/sunrise-sunset/SunriseSunsetDayGuess.ts
Normal file
23
src/routes/sunrise-sunset/SunriseSunsetDayGuess.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { parse } from 'date-fns';
|
||||||
|
import { GuessType } from './GuessType.js';
|
||||||
|
|
||||||
|
export class SunriseSunsetDayGuess {
|
||||||
|
readonly day: Date;
|
||||||
|
readonly emoji: string;
|
||||||
|
|
||||||
|
private static getEmojiForGuessType(guessType: GuessType): string {
|
||||||
|
switch (guessType) {
|
||||||
|
case GuessType.correct:
|
||||||
|
return '🎉';
|
||||||
|
case GuessType.incorrect:
|
||||||
|
return '💔';
|
||||||
|
case GuessType.missing:
|
||||||
|
return '🥷';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(dayString: string, guessType: GuessType) {
|
||||||
|
this.day = parse(dayString, 'yyyy-MM-dd', new Date());
|
||||||
|
this.emoji = SunriseSunsetDayGuess.getEmojiForGuessType(guessType);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/routes/sunrise-sunset/SunriseSunsetGuessSet.ts
Normal file
53
src/routes/sunrise-sunset/SunriseSunsetGuessSet.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { isSameDay, isBefore, addDays, format as formatDate } from 'date-fns';
|
||||||
|
import { SunriseSunsetDayGuess } from './SunriseSunsetDayGuess.js';
|
||||||
|
import { GuessType } from './GuessType.js';
|
||||||
|
|
||||||
|
export class SunriseSunsetDayGuessSet {
|
||||||
|
private readonly sortedGuesses: SunriseSunsetDayGuess[];
|
||||||
|
private readonly sortedGuessesWithMissingDays: SunriseSunsetDayGuess[];
|
||||||
|
|
||||||
|
constructor(guesses: SunriseSunsetDayGuess[]) {
|
||||||
|
this.sortedGuesses = guesses.sort((a, b) => b.day.getTime() - a.day.getTime());
|
||||||
|
const missingGuesses = this.getGuessesForMissingDays();
|
||||||
|
this.sortedGuessesWithMissingDays = SunriseSunsetDayGuessSet.sortGuesses([...guesses, ...missingGuesses]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sortGuesses(guesses: SunriseSunsetDayGuess[]): SunriseSunsetDayGuess[] {
|
||||||
|
return guesses.sort((a, b) => b.day.getTime() - a.day.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
private isGuessPresentForDay(date: Date): boolean {
|
||||||
|
return this.sortedGuesses.some((guess) => isSameDay(guess.day, date));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getGuessesForMissingDays(): SunriseSunsetDayGuess[] {
|
||||||
|
const earliestDate: Date | undefined = this.sortedGuesses[this.sortedGuesses.length - 1]?.day;
|
||||||
|
|
||||||
|
if (!earliestDate) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestDate = this.sortedGuesses[0].day;
|
||||||
|
let guessesForMissingDays: SunriseSunsetDayGuess[] = [];
|
||||||
|
|
||||||
|
let currentDay = earliestDate;
|
||||||
|
|
||||||
|
while (isBefore(currentDay, latestDate)) {
|
||||||
|
const isPresent = this.isGuessPresentForDay(currentDay);
|
||||||
|
|
||||||
|
if (!isPresent) {
|
||||||
|
guessesForMissingDays = [
|
||||||
|
...guessesForMissingDays,
|
||||||
|
new SunriseSunsetDayGuess(formatDate(currentDay, 'yyyy-MM-dd'), GuessType.missing),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
currentDay = addDays(currentDay, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return guessesForMissingDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastDays(maxDays = 10): SunriseSunsetDayGuess[] {
|
||||||
|
return this.sortedGuessesWithMissingDays.slice(0, maxDays);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -88,10 +88,10 @@ describe('SunriseSunsetStreakCalculator', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`Streak Visualisation`, () => {
|
describe(`Streak Visualisation`, () => {
|
||||||
let calculator: SunriseSunsetStreakCalculator;
|
let streakCalculator: SunriseSunsetStreakCalculator;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
calculator = new SunriseSunsetStreakCalculator('2023-01-29');
|
streakCalculator = new SunriseSunsetStreakCalculator('2023-01-29');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`Sound visualise an empty streak`, () => {
|
it(`Sound visualise an empty streak`, () => {
|
||||||
|
|
@ -100,7 +100,7 @@ describe('SunriseSunsetStreakCalculator', () => {
|
||||||
const incorrectDays = [];
|
const incorrectDays = [];
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrectDays);
|
const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrectDays);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
expect(emojiVisualisation).toBe('');
|
expect(emojiVisualisation).toBe('');
|
||||||
|
|
@ -112,19 +112,32 @@ describe('SunriseSunsetStreakCalculator', () => {
|
||||||
const incorrectDays = ['2023-01-19'];
|
const incorrectDays = ['2023-01-19'];
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrectDays);
|
const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrectDays);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
expect(emojiVisualisation).toBe('🎉💔');
|
expect(emojiVisualisation).toBe('🎉💔');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should recognise a streak which has just ended`, () => {
|
||||||
|
// GIVEN
|
||||||
|
const today = new Date('2023-02-01');
|
||||||
|
const correctDays = ['2023-01-31', '2023-01-30'];
|
||||||
|
const calculator = new SunriseSunsetStreakCalculator('2023-02-01');
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
const statement = calculator.getShareableStatement(correctDays, [], today);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
expect(statement).toContain(`Current Streak: 0`);
|
||||||
|
});
|
||||||
|
|
||||||
it(`should handle a missing day`, () => {
|
it(`should handle a missing day`, () => {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
const correctDays = ['2023-01-20', '2023-01-21'];
|
const correctDays = ['2023-01-20', '2023-01-21'];
|
||||||
const incorrect = ['2023-01-18', '2023-01-22'];
|
const incorrect = ['2023-01-18', '2023-01-22'];
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrect);
|
const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrect);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
expect(emojiVisualisation).toBe('💔🎉🎉🥷💔');
|
expect(emojiVisualisation).toBe('💔🎉🎉🥷💔');
|
||||||
|
|
@ -137,7 +150,7 @@ describe('SunriseSunsetStreakCalculator', () => {
|
||||||
const today = new Date('2023-01-20T21:52Z');
|
const today = new Date('2023-01-20T21:52Z');
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const shareableStatement = calculator.getShareableStatement(correctDays, incorrectDays, today);
|
const shareableStatement = streakCalculator.getShareableStatement(correctDays, incorrectDays, today);
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
const expected = `Sunrise, Sunset?\n2023-01-20\n\nCurrent Streak: 0\nLongest Streak: 0`;
|
const expected = `Sunrise, Sunset?\n2023-01-20\n\nCurrent Streak: 0\nLongest Streak: 0`;
|
||||||
|
|
@ -150,6 +163,8 @@ describe('SunriseSunsetStreakCalculator', () => {
|
||||||
const incorrectDays = ['2023-01-28'];
|
const incorrectDays = ['2023-01-28'];
|
||||||
const today = new Date('2023-01-31T21:52Z');
|
const today = new Date('2023-01-31T21:52Z');
|
||||||
|
|
||||||
|
const calculator = new SunriseSunsetStreakCalculator('2023-01-31');
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
const shareableStatement = calculator.getShareableStatement(correctDays, incorrectDays, today);
|
const shareableStatement = calculator.getShareableStatement(correctDays, incorrectDays, today);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,14 @@
|
||||||
import { parse, format as formatDate, isAfter, isBefore, isSameDay, addDays, differenceInCalendarDays } from 'date-fns';
|
import { parse, format as formatDate, differenceInCalendarDays } from 'date-fns';
|
||||||
|
import { SunriseSunsetDayGuess } from './SunriseSunsetDayGuess.js';
|
||||||
enum GuessType {
|
import { SunriseSunsetDayGuessSet } from './SunriseSunsetGuessSet.js';
|
||||||
correct = 'correct',
|
import { GuessType } from './GuessType.js';
|
||||||
incorrect = 'incorrect',
|
|
||||||
missing = 'missing',
|
|
||||||
}
|
|
||||||
|
|
||||||
class SunriseSunsetDayGuess {
|
|
||||||
readonly day: Date;
|
|
||||||
readonly emoji: string;
|
|
||||||
|
|
||||||
private static getEmojiForGuessType(guessType: GuessType): string {
|
|
||||||
switch (guessType) {
|
|
||||||
case GuessType.correct:
|
|
||||||
return '🎉';
|
|
||||||
case GuessType.incorrect:
|
|
||||||
return '💔';
|
|
||||||
case GuessType.missing:
|
|
||||||
return '🥷';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(dayString: string, private readonly guessType: GuessType) {
|
|
||||||
this.day = parse(dayString, 'yyyy-MM-dd', new Date());
|
|
||||||
this.emoji = SunriseSunsetDayGuess.getEmojiForGuessType(guessType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SunriseSunsetDayGuessSet {
|
|
||||||
private readonly sortedGuesses: SunriseSunsetDayGuess[];
|
|
||||||
private readonly sortedGuessesWithMissingDays: SunriseSunsetDayGuess[];
|
|
||||||
|
|
||||||
constructor(guesses: SunriseSunsetDayGuess[]) {
|
|
||||||
this.sortedGuesses = guesses.sort((a, b) => b.day.getTime() - a.day.getTime());
|
|
||||||
const missingGuesses = this.getGuessesForMissingDays();
|
|
||||||
this.sortedGuessesWithMissingDays = SunriseSunsetDayGuessSet.sortGuesses([...guesses, ...missingGuesses]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static sortGuesses(guesses: SunriseSunsetDayGuess[]): SunriseSunsetDayGuess[] {
|
|
||||||
return guesses.sort((a, b) => b.day.getTime() - a.day.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
private isGuessPresentForDay(date: Date): boolean {
|
|
||||||
return this.sortedGuesses.some((guess) => isSameDay(guess.day, date));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getGuessesForMissingDays(): SunriseSunsetDayGuess[] {
|
|
||||||
const earliestDate: Date | undefined = this.sortedGuesses[this.sortedGuesses.length - 1]?.day;
|
|
||||||
|
|
||||||
if (!earliestDate) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestDate = this.sortedGuesses[0].day;
|
|
||||||
let guessesForMissingDays: SunriseSunsetDayGuess[] = [];
|
|
||||||
|
|
||||||
let currentDay = earliestDate;
|
|
||||||
|
|
||||||
while (isBefore(currentDay, latestDate)) {
|
|
||||||
const isPresent = this.isGuessPresentForDay(currentDay);
|
|
||||||
|
|
||||||
if (!isPresent) {
|
|
||||||
guessesForMissingDays = [
|
|
||||||
...guessesForMissingDays,
|
|
||||||
new SunriseSunsetDayGuess(formatDate(currentDay, 'yyyy-MM-dd'), GuessType.missing),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
currentDay = addDays(currentDay, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return guessesForMissingDays;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastDays(maxDays = 10): SunriseSunsetDayGuess[] {
|
|
||||||
return this.sortedGuessesWithMissingDays.slice(0, maxDays);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SunriseSunsetStreak {
|
class SunriseSunsetStreak {
|
||||||
readonly longestStreak: number;
|
readonly longestStreak: number;
|
||||||
private readonly allStreaks: number[];
|
private readonly allStreaks: number[];
|
||||||
readonly mostRecentStreak: number;
|
readonly mostRecentStreak: number;
|
||||||
|
|
||||||
constructor(correctDays: Date[]) {
|
constructor(correctDays: Date[], today = new Date()) {
|
||||||
if (correctDays.length === 0) {
|
if (correctDays.length === 0) {
|
||||||
this.longestStreak = 0;
|
this.longestStreak = 0;
|
||||||
this.mostRecentStreak = 0;
|
this.mostRecentStreak = 0;
|
||||||
|
|
@ -155,20 +81,18 @@ export class SunriseSunsetStreakCalculator {
|
||||||
const emoji = this.getEmojiForHistory(correctDays, incorrectDays);
|
const emoji = this.getEmojiForHistory(correctDays, incorrectDays);
|
||||||
const todayFormatted = formatDate(today, 'yyyy-MM-dd');
|
const todayFormatted = formatDate(today, 'yyyy-MM-dd');
|
||||||
const streak = new SunriseSunsetStreak(this.daysAsDates(correctDays));
|
const streak = new SunriseSunsetStreak(this.daysAsDates(correctDays));
|
||||||
|
const currentStreak = this.getStreakLength(correctDays);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`Sunrise, Sunset?`,
|
`Sunrise, Sunset?`,
|
||||||
todayFormatted,
|
todayFormatted,
|
||||||
emoji,
|
emoji,
|
||||||
`Current Streak: ${streak.mostRecentStreak}`,
|
`Current Streak: ${currentStreak}`,
|
||||||
`Longest Streak: ${streak.longestStreak}`,
|
`Longest Streak: ${streak.longestStreak}`,
|
||||||
].join(joiningString);
|
].join(joiningString);
|
||||||
}
|
}
|
||||||
|
|
||||||
getStreakLength(correctDays: string[]): number {
|
getStreakLength(correctDays: string[]): number {
|
||||||
console.log(`getStreakLength`);
|
|
||||||
console.log(correctDays);
|
|
||||||
|
|
||||||
if (correctDays.length === 0) {
|
if (correctDays.length === 0) {
|
||||||
console.log(`No correct days, returning 0.`);
|
console.log(`No correct days, returning 0.`);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue