挑战系统说明
介绍「认真喝水」18 项饮水挑战的分类、判定规则、进度计算方式与界面行为。挑战为纯函数实时计算,无独立持久化。
最后更新:2026-06-20
挑战页(Tab「挑战」)通过 ChallengeEngine 根据本地饮水记录实时计算进度,不写入 CloudKit,也没有「领取奖励」或等级体系。每项挑战展示 本轮进度 与 累计完成次数。
设计意图:规则简单、可解释,用户只看记录数据就能理解「还差什么」;避免复杂游戏化状态。
一、总体结构
DrinkRecord(全部历史记录)
+
DailyGoalSnapshot(每日目标快照)
+
UserProfile(今日目标、饮品库列表)
↓
ChallengeEngine(纯函数,无持久化)
↓
ChallengesView(展示、筛选、详情)
| 组件 | 路径 | 职责 |
|---|---|---|
ChallengeEngine | WaterMind/Models/ChallengeEngine.swift | 18 项挑战的判定与进度计算 |
ChallengesView | WaterMind/Views/Challenges/ChallengesView.swift | 挑战 Tab UI、分类筛选、详情弹层 |
| 单元测试 | WaterMindTests/ChallengeEngineTests.swift | 历史目标快照对达标判定的影响 |
二、核心约定
2.1 饮水量怎么算
- 每日总量、达标判定一律使用
effectiveML(volumeML × hydrationRate,记录时冻结)。 - 补水率为负的饮品(如部分酒类)会扣减当日有效饮水量,可能影响「满杯」类挑战。
- 「有饮水记录」仅看当天是否存在至少一条
DrinkRecord,与容量正负无关。
2.2 「每日目标」用哪天的数
- 今天:
UserProfile.dailyGoalML(含智能规划、手动目标等当前逻辑)。 - 历史某天:优先读
DailyGoalSnapshot固化值,防止日后改设置重写历史达标。 - 无快照的历史日 fallback 见
DailyGoalStore(与统计页一致)。
例:昨天快照目标 1000 ml、今天目标 3000 ml;昨天喝了 1500 ml,则昨天算达标、今天按 3000 ml 判。
2.3 时间与时区
- 全部按 设备本地日历(
Calendar.current)切日、切周、切月。 - 周起始日为周一(
weekStart:(weekday + 5) % 7回退到周一 0 点)。 - 时段挑战按记录
timestamp的小时 判定(见下文各每日挑战)。
2.4 进度与完成次数
| 字段 | 含义 |
|---|---|
progress | 0…1,本轮进度条 |
progressText | 如 3 / 5 天、1200 / 2000 ml |
completionCount | 历史上累计完成轮数(非「已领取次数」) |
nextStepText | 距本轮完成还差什么,或「今天/本周/本月已完成」 |
无持久化:关闭 App 再开,进度由最新记录重新算;不存在「漏领勋章」。
2.5 不做什么
- 无 CloudKit 同步、无 UserDefaults 缓存挑战状态
- 无等级、无积分商城、无手动「领取」
- 探索类「饮品库」计数来自
UserProfile.recentDrinkTypeRaws(饮品库/快捷栏已保存的饮品 ref 数量),非全 App 内置目录总数
三、六大分类 · 18 项挑战
3.1 每日(4 项)— 每天可完成 1 次
completionCount = 历史上满足条件的天数总和。
| ID | 名称 | 完成条件 | 时段/说明 |
|---|---|---|---|
daily.morning | 早安,第一杯! | 当天 10:00 前 记录 ≥1 次 | 小时 < 10 |
daily.afternoon | 午后补给 | 当天 14:00–16:59 记录 ≥1 次 | [14, 17) |
daily.rhythm | 节奏大师 | 四个时段各记录 ≥1 次 | 0–10、10–14、14–18、18–24 |
daily.full | 满杯! | 当天 effectiveML 合计 ≥ 当日目标 | 进度条按目标 ml 显示 |
3.2 连续(3 项)— 每连续 N 天完成 1 轮
从今天或昨天起向前数连续满足条件的天数;中断则本轮进度归零。
| ID | 名称 | 连续条件 | 轮次 |
|---|---|---|---|
streak.three | 三天连击 | 连续 3 天有记录 | 每满 3 天 +1 次完成 |
streak.week | 一周不断流 | 连续 7 天有记录 | 每满 7 天 +1 次 |
streak.fullWeek | 满杯七连 | 连续 7 天达标 | 每满 7 天 +1 次 |
completionCount = 历史上所有连续段长度之和 ÷ 目标天数(整除)。
3.3 每周(3 项)— 每周最多完成 1 次
统计 本周一 0 点 起至今天(含)的 7 日窗口;历史周按同样规则回看。
| ID | 名称 | 完成条件 |
|---|---|---|
weekly.online | 本周在线 | 本周 ≥5 天有记录 |
weekly.full | 五天满杯! | 本周 ≥5 天达标 |
weekly.logs | 记录达人 | 本周累计记录 ≥20 次 |
3.4 每月(3 项)— 每月最多完成 1 次
统计 本月 1 日 0 点 起至今天(含)。
| ID | 名称 | 完成条件 |
|---|---|---|
monthly.regular | 月度常客 | 本月 ≥20 天有记录 |
monthly.full | 满杯半月 | 本月 ≥15 天达标 |
monthly.logs | 一百次! | 本月累计记录 ≥100 次 |
3.5 探索(3 项)— 可重复多轮
基于全部历史记录或饮品库;每凑满一轮目标 completionCount +1,进度条显示当前轮余量。
| ID | 名称 | 完成条件 | 计数来源 |
|---|---|---|---|
explore.flavors | 换个口味! | 每累计 3 种不同饮品名 | Set(records.map(\.itemName)) |
explore.volumes | 杯杯不同 | 每累计 5 种不同容量 | Set(records.map(\.volumeML)) |
explore.library | 我的饮品柜 | 每拥有 8 种饮品 | recentDrinkTypeRaws.count |
3.6 累计(2 项)— 长期里程碑,可重复多轮
| ID | 名称 | 完成条件 |
|---|---|---|
milestone.logs | 百记不停 | 每累计记录 100 次 |
milestone.goalDays | 满杯收藏家 | 每累计 30 个达标日 |
四、界面行为(ChallengesView)
4.1 数据注入
ChallengeEngine(
records: allRecords, // @Query 全部 DrinkRecord
goalML: profile.dailyGoalML, // 今日执行目标
goalsByDay: DailyGoalStore.goalsByDay(...), // 历史快照
libraryCount: profile.recentDrinkTypeRaws.count
)
每次进入挑战 Tab 或记录变化触发 SwiftUI 刷新时,引擎重新计算。
4.2 「离完成最近」推荐卡
在 每日 / 连续 / 每周 三类中,取 progress < 1 且进度最高的一项置顶;若均已满则 fallback 到列表第一项。
4.3 分类筛选
顶部分类 Chip:全部 + 六大类 ChallengeCategory。选中后网格只显示该分类挑战。
4.4 详情弹层
点击卡片打开 ChallengeDetailSheet,展示:
- 完成条件(
condition) - 本轮进度与完成次数
- 周期说明(
cycleText) - 贴纸勋章视觉(
StickerBadge)
五、与产品其他模块的关系
| 模块 | 关系 |
|---|---|
| 今日 / 记录 | 新增、删除记录会改变挑战进度 |
| 智能规划 | 仅影响「今日目标」及未来快照,不改变挑战规则本身 |
| 统计页 | 共用 DailyGoalStore.goalsByDay 思路,达标定义一致 |
| Widget | 不展示挑战 |
| 备份 | 备份 DrinkRecord / DailyGoalSnapshot / UserProfile 即可间接恢复挑战进度 |
六、修改挑战时请同步
ChallengeEngine.swift— 规则与文案(title/condition/cycleText)ChallengeEngineTests.swift— 边界用例(历史目标快照、连续中断、周/月边界等)- 本文档 — 规则表与约定说明
七、快速对照表(18 项)
| # | 分类 | 名称 | 周期 |
|---|---|---|---|
| 1 | 每日 | 早安,第一杯! | 每天 |
| 2 | 每日 | 午后补给 | 每天 |
| 3 | 每日 | 节奏大师 | 每天 |
| 4 | 每日 | 满杯! | 每天 |
| 5 | 连续 | 三天连击 | 每连续 3 天 |
| 6 | 连续 | 一周不断流 | 每连续 7 天 |
| 7 | 连续 | 满杯七连 | 每连续 7 天 |
| 8 | 每周 | 本周在线 | 每周 |
| 9 | 每周 | 五天满杯! | 每周 |
| 10 | 每周 | 记录达人 | 每周 |
| 11 | 每月 | 月度常客 | 每月 |
| 12 | 每月 | 满杯半月 | 每月 |
| 13 | 每月 | 一百次! | 每月 |
| 14 | 探索 | 换个口味! | 每 3 种饮品 |
| 15 | 探索 | 杯杯不同 | 每 5 种容量 |
| 16 | 探索 | 我的饮品柜 | 每 8 种库内饮品 |
| 17 | 累计 | 百记不停 | 每 100 次记录 |
| 18 | 累计 | 满杯收藏家 | 每 30 个达标日 |