feat(daily): pure 7-day login-calendar logic + reward table

This commit is contained in:
2026-06-18 12:11:15 +09:00
parent 544a2b8be4
commit c185bd0886
2 changed files with 118 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
// lib/game/daily/daily_reward.dart
import '../models/booster.dart';
/// The result of evaluating the calendar for "today".
class DailyResolution {
const DailyResolution({required this.claimable, required this.day});
final bool claimable;
final int day; // 1..7, the day to show/grant
}
/// Pure 7-day login-calendar logic. No storage or clock — callers pass the
/// persisted state and today's date so it is fully unit-testable.
class DailyRewardCalendar {
const DailyRewardCalendar();
static const int cycle = 7;
String ymd(DateTime date) {
final m = date.month.toString().padLeft(2, '0');
final d = date.day.toString().padLeft(2, '0');
return '${date.year}-$m-$d';
}
DailyResolution resolve({
required String? lastClaimedYmd,
required int storedDay,
required DateTime today,
}) {
final todayYmd = ymd(today);
if (lastClaimedYmd == todayYmd) {
return DailyResolution(claimable: false, day: storedDay);
}
final yesterday = ymd(today.subtract(const Duration(days: 1)));
final int day;
if (lastClaimedYmd == yesterday) {
day = (storedDay % cycle) + 1; // advance, wrapping 7 -> 1
} else {
day = 1; // first ever, or a day was missed
}
return DailyResolution(claimable: true, day: day);
}
/// Boosters granted for a given calendar day (1..7). Tunable.
Map<BoosterType, int> rewardFor(int day) {
switch (day) {
case 1:
return {BoosterType.hammer: 1};
case 2:
return {BoosterType.shuffle: 1};
case 3:
return {BoosterType.lineBomb: 1};
case 4:
return {BoosterType.hammer: 1, BoosterType.shuffle: 1};
case 5:
return {BoosterType.shuffle: 1, BoosterType.lineBomb: 1};
case 6:
return {BoosterType.hammer: 1, BoosterType.lineBomb: 1};
case 7:
default:
return {
BoosterType.hammer: 2,
BoosterType.shuffle: 2,
BoosterType.lineBomb: 2,
};
}
}
}