diff --git a/src/routes/sunrise-sunset/GuessType.ts b/src/routes/sunrise-sunset/GuessType.ts new file mode 100644 index 0000000..b30efc0 --- /dev/null +++ b/src/routes/sunrise-sunset/GuessType.ts @@ -0,0 +1,5 @@ +export enum GuessType { + correct = 'correct', + incorrect = 'incorrect', + missing = 'missing', +} diff --git a/src/routes/sunrise-sunset/ScoreCardSection.svelte b/src/routes/sunrise-sunset/ScoreCardSection.svelte index cfc4a8b..5eca183 100644 --- a/src/routes/sunrise-sunset/ScoreCardSection.svelte +++ b/src/routes/sunrise-sunset/ScoreCardSection.svelte @@ -8,7 +8,6 @@ export let currentStreakLength: number; const todayAsString = formatDate(new Date(), "yyyy-MM-dd"); - $: totalGuessCount = incorrectGuessDays.length + correctGuessDays.length; const calculator = new SunriseSunsetStreakCalculator(todayAsString); let hasTextBeenCopied = false; diff --git a/src/routes/sunrise-sunset/SunriseSunsetDayGuess.ts b/src/routes/sunrise-sunset/SunriseSunsetDayGuess.ts new file mode 100644 index 0000000..32c7ede --- /dev/null +++ b/src/routes/sunrise-sunset/SunriseSunsetDayGuess.ts @@ -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); + } +} diff --git a/src/routes/sunrise-sunset/SunriseSunsetGuessSet.ts b/src/routes/sunrise-sunset/SunriseSunsetGuessSet.ts new file mode 100644 index 0000000..e6a5c0d --- /dev/null +++ b/src/routes/sunrise-sunset/SunriseSunsetGuessSet.ts @@ -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); + } +} diff --git a/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.test.ts b/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.test.ts index 8af40a8..87094b9 100644 --- a/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.test.ts +++ b/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.test.ts @@ -88,10 +88,10 @@ describe('SunriseSunsetStreakCalculator', () => { }); describe(`Streak Visualisation`, () => { - let calculator: SunriseSunsetStreakCalculator; + let streakCalculator: SunriseSunsetStreakCalculator; beforeAll(() => { - calculator = new SunriseSunsetStreakCalculator('2023-01-29'); + streakCalculator = new SunriseSunsetStreakCalculator('2023-01-29'); }); it(`Sound visualise an empty streak`, () => { @@ -100,7 +100,7 @@ describe('SunriseSunsetStreakCalculator', () => { const incorrectDays = []; // WHEN - const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrectDays); + const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrectDays); // THEN expect(emojiVisualisation).toBe(''); @@ -112,19 +112,32 @@ describe('SunriseSunsetStreakCalculator', () => { const incorrectDays = ['2023-01-19']; // WHEN - const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrectDays); + const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrectDays); // THEN 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`, () => { // GIVEN const correctDays = ['2023-01-20', '2023-01-21']; const incorrect = ['2023-01-18', '2023-01-22']; // WHEN - const emojiVisualisation = calculator.getEmojiForHistory(correctDays, incorrect); + const emojiVisualisation = streakCalculator.getEmojiForHistory(correctDays, incorrect); // THEN expect(emojiVisualisation).toBe('💔🎉🎉🥷💔'); @@ -137,7 +150,7 @@ describe('SunriseSunsetStreakCalculator', () => { const today = new Date('2023-01-20T21:52Z'); // WHEN - const shareableStatement = calculator.getShareableStatement(correctDays, incorrectDays, today); + const shareableStatement = streakCalculator.getShareableStatement(correctDays, incorrectDays, today); // THEN 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 today = new Date('2023-01-31T21:52Z'); + const calculator = new SunriseSunsetStreakCalculator('2023-01-31'); + // WHEN const shareableStatement = calculator.getShareableStatement(correctDays, incorrectDays, today); diff --git a/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.ts b/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.ts index ca6b93a..e05da1e 100644 --- a/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.ts +++ b/src/routes/sunrise-sunset/SunriseSunsetStreakCalculator.ts @@ -1,88 +1,14 @@ -import { parse, format as formatDate, isAfter, isBefore, isSameDay, addDays, differenceInCalendarDays } from 'date-fns'; - -enum GuessType { - correct = 'correct', - 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); - } -} +import { parse, format as formatDate, differenceInCalendarDays } from 'date-fns'; +import { SunriseSunsetDayGuess } from './SunriseSunsetDayGuess.js'; +import { SunriseSunsetDayGuessSet } from './SunriseSunsetGuessSet.js'; +import { GuessType } from './GuessType.js'; class SunriseSunsetStreak { readonly longestStreak: number; private readonly allStreaks: number[]; readonly mostRecentStreak: number; - constructor(correctDays: Date[]) { + constructor(correctDays: Date[], today = new Date()) { if (correctDays.length === 0) { this.longestStreak = 0; this.mostRecentStreak = 0; @@ -155,20 +81,18 @@ export class SunriseSunsetStreakCalculator { const emoji = this.getEmojiForHistory(correctDays, incorrectDays); const todayFormatted = formatDate(today, 'yyyy-MM-dd'); const streak = new SunriseSunsetStreak(this.daysAsDates(correctDays)); + const currentStreak = this.getStreakLength(correctDays); return [ `Sunrise, Sunset?`, todayFormatted, emoji, - `Current Streak: ${streak.mostRecentStreak}`, + `Current Streak: ${currentStreak}`, `Longest Streak: ${streak.longestStreak}`, ].join(joiningString); } getStreakLength(correctDays: string[]): number { - console.log(`getStreakLength`); - console.log(correctDays); - if (correctDays.length === 0) { console.log(`No correct days, returning 0.`); return 0;