feat(daily): pure 7-day login-calendar logic + reward table
This commit is contained in:
@@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:block_seasons/game/daily/daily_reward.dart';
|
||||||
|
import 'package:block_seasons/game/models/booster.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final cal = DailyRewardCalendar();
|
||||||
|
|
||||||
|
DateTime d(int day) => DateTime(2026, 6, day);
|
||||||
|
|
||||||
|
test('first ever claim is day 1', () {
|
||||||
|
final r = cal.resolve(lastClaimedYmd: null, storedDay: 0, today: d(18));
|
||||||
|
expect(r.claimable, isTrue);
|
||||||
|
expect(r.day, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('claiming again the same day is not claimable', () {
|
||||||
|
final r = cal.resolve(
|
||||||
|
lastClaimedYmd: '2026-06-18', storedDay: 1, today: d(18));
|
||||||
|
expect(r.claimable, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a consecutive day advances the calendar', () {
|
||||||
|
final r = cal.resolve(
|
||||||
|
lastClaimedYmd: '2026-06-18', storedDay: 3, today: d(19));
|
||||||
|
expect(r.claimable, isTrue);
|
||||||
|
expect(r.day, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('day 7 wraps back to day 1', () {
|
||||||
|
final r = cal.resolve(
|
||||||
|
lastClaimedYmd: '2026-06-18', storedDay: 7, today: d(19));
|
||||||
|
expect(r.day, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a missed day resets to day 1', () {
|
||||||
|
final r = cal.resolve(
|
||||||
|
lastClaimedYmd: '2026-06-18', storedDay: 4, today: d(20));
|
||||||
|
expect(r.claimable, isTrue);
|
||||||
|
expect(r.day, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reward table covers days 1..7 and day 7 is the jackpot', () {
|
||||||
|
expect(cal.rewardFor(1), {BoosterType.hammer: 1});
|
||||||
|
expect(cal.rewardFor(7),
|
||||||
|
{BoosterType.hammer: 2, BoosterType.shuffle: 2, BoosterType.lineBomb: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ymd formats a date as YYYY-MM-DD', () {
|
||||||
|
expect(cal.ymd(DateTime(2026, 6, 7)), '2026-06-07');
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user