198 lines
7.1 KiB
TypeScript
198 lines
7.1 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
|
|
class SunriseSunsetStreak {
|
|
readonly longestStreak: number;
|
|
private readonly allStreaks: number[];
|
|
readonly mostRecentStreak: number;
|
|
|
|
constructor(correctDays: Date[]) {
|
|
if (correctDays.length === 0) {
|
|
this.longestStreak = 0;
|
|
this.mostRecentStreak = 0;
|
|
this.allStreaks = [];
|
|
return;
|
|
}
|
|
|
|
// A (reverse-order) list of streaks in the lis tof correct days.
|
|
const sortedDays = correctDays.sort((a, b) => b.getTime() - a.getTime());
|
|
|
|
const allStreaks: number[] = sortedDays.reduce(
|
|
(streaks: number[], day, index, days) => {
|
|
const currentStreakLength = streaks.at(-1);
|
|
|
|
const daysBetween = differenceInCalendarDays(day, days.at(index + 1));
|
|
|
|
console.log(
|
|
streaks,
|
|
`Day ${index}: ${formatDate(
|
|
day,
|
|
'yyyy-MM-dd'
|
|
)}, streak length: ${currentStreakLength}, days between: ${daysBetween}`
|
|
);
|
|
|
|
if (isNaN(daysBetween)) {
|
|
// We're on the last day, so just return the streaks
|
|
return streaks;
|
|
} else if (daysBetween === 1) {
|
|
// Add one to the last item in the streaks array
|
|
return [...streaks.slice(0, -1), currentStreakLength + 1];
|
|
} else {
|
|
// We've hit a gap, so start a new streak
|
|
return [...streaks, 1];
|
|
}
|
|
},
|
|
[1]
|
|
);
|
|
|
|
// The streaks are in reverse order, so the most recent streak is the last one.
|
|
console.log('allStreaks', allStreaks);
|
|
this.mostRecentStreak = allStreaks[0] ?? 0;
|
|
this.longestStreak = allStreaks.length > 0 ? Math.max(...allStreaks) : 0;
|
|
this.allStreaks = allStreaks;
|
|
}
|
|
}
|
|
|
|
export class SunriseSunsetStreakCalculator {
|
|
private readonly todayDate: Date;
|
|
constructor(private readonly today: string) {
|
|
this.todayDate = parse(today, 'yyyy-MM-dd', new Date());
|
|
}
|
|
|
|
private daysAsDates(dayStrings: string[]): Date[] {
|
|
return dayStrings.map((day) => parse(day, 'yyyy-MM-dd', new Date()));
|
|
}
|
|
|
|
getEmojiForHistory(correctDays: string[], incorrectDays: string[], length = 7): string {
|
|
const correctGuesses = correctDays.map((day) => new SunriseSunsetDayGuess(day, GuessType.correct));
|
|
const incorrectGuesses = incorrectDays.map((day) => new SunriseSunsetDayGuess(day, GuessType.incorrect));
|
|
const allGuesses = new SunriseSunsetDayGuessSet([...correctGuesses, ...incorrectGuesses]);
|
|
|
|
const streak: string = allGuesses.getLastDays(length).reduce((streak, guess) => {
|
|
return streak + guess.emoji;
|
|
}, '');
|
|
|
|
return streak;
|
|
}
|
|
|
|
getShareableStatement(correctDays: string[], incorrectDays: string[], today: Date, joiningString = '\n'): string {
|
|
const emoji = this.getEmojiForHistory(correctDays, incorrectDays);
|
|
const todayFormatted = formatDate(today, 'yyyy-MM-dd');
|
|
const streak = new SunriseSunsetStreak(this.daysAsDates(correctDays));
|
|
|
|
return [
|
|
`Sunrise, Sunset?`,
|
|
todayFormatted,
|
|
emoji,
|
|
`Current Streak: ${streak.mostRecentStreak}`,
|
|
`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;
|
|
} else if (!correctDays.some((day) => day === this.today)) {
|
|
console.log(`Today is not in the list of correct days`);
|
|
return 0;
|
|
}
|
|
|
|
const daysAsDates = this.daysAsDates(correctDays);
|
|
|
|
const sortedDaysWithoutToday = correctDays
|
|
.filter((day) => day !== this.today)
|
|
.map((day) => parse(day, 'yyyy-MM-dd', new Date()))
|
|
.sort((a, b) => b.getTime() - a.getTime());
|
|
|
|
const daysBetweenTodayAndMostRecentDay = differenceInCalendarDays(this.todayDate, sortedDaysWithoutToday[0]);
|
|
|
|
if (daysBetweenTodayAndMostRecentDay > 1) {
|
|
console.log(`Today is more than one day after the most recent correct day`);
|
|
return 1;
|
|
}
|
|
|
|
const streaks = new SunriseSunsetStreak(daysAsDates);
|
|
|
|
return streaks.mostRecentStreak;
|
|
}
|
|
}
|