文章目录
- [1 问题描述](#1 问题描述)
- [2 旧版算法的局限性](#2 旧版算法的局限性)
- [3 CSP 求解器模型](#3 CSP 求解器模型)
-
- [3.1 约束满足问题建模](#3.1 约束满足问题建模)
-
- [3.1.1 植物成分的形式化表示](#3.1.1 植物成分的形式化表示)
- [3.1.2 反馈约束的形式化](#3.1.2 反馈约束的形式化)
- [3.1.3 解空间规模](#3.1.3 解空间规模)
- [3.2 约束传播机制](#3.2 约束传播机制)
-
- [3.2.1 域更新的数学表达](#3.2.1 域更新的数学表达)
- [3.2.2 全局约束传播](#3.2.2 全局约束传播)
- [3.2.3 反馈模拟函数](#3.2.3 反馈模拟函数)
- [3.3 Minimax 搜索策略](#3.3 Minimax 搜索策略)
-
- [3.3.1 算法原理](#3.3.1 算法原理)
- [3.3.2 搜索优化](#3.3.2 搜索优化)
- [3.4 算法流程](#3.4 算法流程)
-
- [3.4.1 第 1 轮:成分覆盖](#3.4.1 第 1 轮:成分覆盖)
- [3.4.2 第2 \~ 5轮:CSP 约束传播 + Minimax 搜索](#3.4.2 第2 ~ 5轮:CSP 约束传播 + Minimax 搜索)
- [4 实现细节](#4 实现细节)
-
- [4.1 数据结构设计](#4.1 数据结构设计)
-
- [4.1.1 Plant 类](#4.1.1 Plant 类)
- [4.1.2 Feedback 枚举](#4.1.2 Feedback 枚举)
- [4.1.3 CSPSolver 核心状态](#4.1.3 CSPSolver 核心状态)
- [4.2 约束传播实现](#4.2 约束传播实现)
- [4.3 Minimax 搜索实现](#4.3 Minimax 搜索实现)
- [5 测试结果](#5 测试结果)
-
- [5.1 测试数据](#5.1 测试数据)
- [5.2 结果分析](#5.2 结果分析)
- [6 可视化界面](#6 可视化界面)
- [7 小结](#7 小结)
在本文中将在 上一篇 的基础上利用 CSP 求解器进一步将算法的金奖杯率,这也是最终的解决方案。这里首先重述一下问题。
1 问题描述
在《植物大战僵尸 3:进化》中,有一个解码小游戏,要求猜出一个 4 个 2 层植物组合。其中,可供选择的 1 层植物为向日葵、豌豆射手、卷心菜投手、坚果墙、地刺,2 个 1 层植物可以合成为 1 个 2 层植物,合成关系如下表[1](#1):
| 向日葵 | 豌豆射手 | 卷心菜投手 | 坚果墙 | 地刺 | |
|---|---|---|---|---|---|
| 向日葵 | 双胞向日葵 | 太阳能豌豆 | 葵花籽 | ||
| 豌豆射手 | 太阳能豌豆 | 双重射手 | 豌豆迫击炮 | 花生射手 | 松针射手 |
| 卷心菜投手 | 豌豆迫击炮 | 卷心菜连投手 | 栗子投手 | ||
| 坚果墙 | 葵花籽 | 花生射手 | 栗子投手 | 高坚果 | 荔枝 |
| 地刺 | 松针射手 | 荔枝 | 锯齿草 |
在开始游戏时,系统会自动生成一个不重复的 2 层植物组合,玩家需要给出猜测。每次给出一个猜测,系统会逐一比较每个位置上的植物,并返回 4 种结果:
- 正确
- 可交换:当前位置不正确,但有另一个位置是这个答案
- 部分正确:有一个合成植物猜对了
- 完全错误:除以上情况外的返回
如果没猜中,玩家需要根据上述结果修改自己的答案,直至猜中为止。每个玩家都有 15 次机会进行猜测,只有 5 次及以内猜中的才可获得金奖杯。问:执行怎样的策略才能稳定地获得金奖杯?
2 旧版算法的局限性
在 上一篇 中,我们设计了一种三阶段策略:第一轮成分覆盖,第二轮查表与频率分析,第三至五轮排列求解。经过测试,该算法能在 6 轮内找到正确答案,其中约 99% 的用例可在 5 轮内求解,获得金奖杯。
然而,这种启发式方法存在明显的局限性。核心问题在于信息利用不充分:第一轮反馈后,旧算法虽然更新了知识库,但在第二轮猜测时依赖预查表和简单的频率分析,无法精准选择最优猜测。例如,当第一轮反馈为 WWWW (完全错误) 时,算法会默认选择双胞向日葵作为第二轮的首位植物,但此时第一位已经确认不含向日葵成分,这导致成分约束信息被忽略,产生了冗余猜测。
此外,旧算法在排列求解阶段仅测试候选植物的排列,而没有利用 Minimax 策略 (最小化最坏情况) 来选择猜测,使得某些困难用例需要额外轮数才能求解。为了进一步提升金奖杯率至 100%,我们需要引入更严谨的数学模型和更高效的搜索算法。
3 CSP 求解器模型
为了克服旧版算法的局限性,我们将解码小游戏建模为约束满足问题 (Constraint Satisfaction Problem, CSP),并利用约束传播和 Minimax 搜索相结合的策略进行求解。
3.1 约束满足问题建模
CSP 由三个要素组成:变量、域和约束。在本问题中:
- 变量 :4 个位置的植物选择,记作 X 0 , X 1 , X 2 , X 3 X_0, X_1, X_2, X_3 X0,X1,X2,X3。
- 域 :每个变量的可能植物集合,初始时 D ( X i ) = { P 1 , P 2 , ... , P 12 } D(X_i) = \{P_1, P_2, \dots, P_{12}\} D(Xi)={P1,P2,...,P12},其中 P j P_j Pj 表示第 j j j 种 2 层植物。
- 约束 :
- 互异性约束 :4 个位置的植物必须互不相同,即 ∀ i ≠ j , X i ≠ X j \forall i \neq j, X_i \neq X_j ∀i=j,Xi=Xj。
- 反馈约束:每轮反馈都会对域产生限制,具体形式见下文。
3.1.1 植物成分的形式化表示
每种植物 P j P_j Pj 包含两个 1 层成分,我们用位掩码进行形式化表示。设成分集合为 E = { A , B , C , D , E } \mathcal{E} = \{A, B, C, D, E\} E={A,B,C,D,E},对应索引 0 , 1 , 2 , 3 , 4 0, 1, 2, 3, 4 0,1,2,3,4。植物 P j P_j Pj 的成分掩码定义为:
mask ( P j ) = 2 e 1 + 2 e 2 \text{mask}(P_j) = 2^{e_1} + 2^{e_2} mask(Pj)=2e1+2e2
其中 e 1 , e 2 ∈ { 0 , 1 , 2 , 3 , 4 } e_1, e_2 \in \{0, 1, 2, 3, 4\} e1,e2∈{0,1,2,3,4} 是该植物的两个成分索引。例如,太阳能豌豆 ( P 2 P_2 P2) 包含成分 A 和 B,其掩码为 mask ( P 2 ) = 2 0 + 2 1 = 3 \text{mask}(P_2) = 2^0 + 2^1 = 3 mask(P2)=20+21=3。
两种植物 P i P_i Pi 和 P j P_j Pj 的成分共享关系可通过位运算快速判断:
sharesAny ( P i , P j ) ⟺ mask ( P i ) ∧ mask ( P j ) ≠ 0 \text{sharesAny}(P_i, P_j) \iff \text{mask}(P_i) \land \text{mask}(P_j) \neq 0 sharesAny(Pi,Pj)⟺mask(Pi)∧mask(Pj)=0
sharesNone ( P i , P j ) ⟺ mask ( P i ) ∧ mask ( P j ) = 0 \text{sharesNone}(P_i, P_j) \iff \text{mask}(P_i) \land \text{mask}(P_j) = 0 sharesNone(Pi,Pj)⟺mask(Pi)∧mask(Pj)=0
其中 ∧ \land ∧ 表示按位与运算。
3.1.2 反馈约束的形式化
设答案序列为 A = ( A 0 , A 1 , A 2 , A 3 ) \boldsymbol{A} = (A_0, A_1, A_2, A_3) A=(A0,A1,A2,A3),猜测序列为 G = ( G 0 , G 1 , G 2 , G 3 ) \boldsymbol{G} = (G_0, G_1, G_2, G_3) G=(G0,G1,G2,G3)。对于每个位置 i i i,系统返回反馈 F i ∈ { C , S , P , W } F_i \in \{C, S, P, W\} Fi∈{C,S,P,W}。每种反馈对应的数学约束如下:
- C (正确) : G i = A i G_i = A_i Gi=Ai。此时 X i X_i Xi 被锁定为 G i G_i Gi,且 ∀ j ≠ i , X j ≠ G i \forall j \neq i, X_j \neq G_i ∀j=i,Xj=Gi。
- S (可交换) : G i ≠ A i ∧ ∃ j ≠ i , A j = G i G_i \neq A_i \land \exists j \neq i, A_j = G_i Gi=Ai∧∃j=i,Aj=Gi。此时 X i ≠ G i X_i \neq G_i Xi=Gi,但 G i ∈ { A 0 , A 1 , A 2 , A 3 } G_i \in \{A_0, A_1, A_2, A_3\} Gi∈{A0,A1,A2,A3}。
- P (部分正确) : G i ∉ { A 0 , A 1 , A 2 , A 3 } ∧ sharesAny ( G i , A i ) G_i \notin \{A_0, A_1, A_2, A_3\} \land \text{sharesAny}(G_i, A_i) Gi∈/{A0,A1,A2,A3}∧sharesAny(Gi,Ai)。此时 X i ≠ G i X_i \neq G_i Xi=Gi,且 ∀ P k ∈ D ( X i ) \forall P_k \in D(X_i) ∀Pk∈D(Xi),若 sharesNone ( P k , G i ) \text{sharesNone}(P_k, G_i) sharesNone(Pk,Gi) 则 P k ∉ D ( X i ) P_k \notin D(X_i) Pk∈/D(Xi)。
- W (完全错误) : G i ∉ { A 0 , A 1 , A 2 , A 3 } ∧ sharesNone ( G i , A i ) G_i \notin \{A_0, A_1, A_2, A_3\} \land \text{sharesNone}(G_i, A_i) Gi∈/{A0,A1,A2,A3}∧sharesNone(Gi,Ai)。此时 X i ≠ G i X_i \neq G_i Xi=Gi,且 ∀ P k ∈ D ( X i ) \forall P_k \in D(X_i) ∀Pk∈D(Xi),若 sharesAny ( P k , G i ) \text{sharesAny}(P_k, G_i) sharesAny(Pk,Gi) 则 P k ∉ D ( X i ) P_k \notin D(X_i) Pk∈/D(Xi)。
3.1.3 解空间规模
初始解空间大小为从 12 种植物中选择 4 种并排列的总数:
∣ S 0 ∣ = P ( 12 , 4 ) = 12 ⋅ 11 ⋅ 10 ⋅ 9 = 11880 |\mathcal{S}_0| = P(12, 4) = 12 \cdot 11 \cdot 10 \cdot 9 = 11880 ∣S0∣=P(12,4)=12⋅11⋅10⋅9=11880
每轮反馈后,解空间会被约束传播缩小。设第 r r r 轮后的解空间为 S r \mathcal{S}_r Sr,则理想情况下 ∣ S r ∣ |\mathcal{S}_r| ∣Sr∣ 应快速收敛至 1。
3.2 约束传播机制
约束传播是 CSP 求解的核心。每次收到反馈后,算法会根据反馈类型更新对应位置的域,并传播约束到其余位置。具体规则如下:
| 反馈类型 | 语义 | 约束传播操作 |
|---|---|---|
| C (正确) | 该位置植物正确 | 锁定该位置为该植物,加入答案集合,从其他位置的域中移除 |
| S (可交换) | 植物正确但位置错误 | 加入答案集合,该位置排除此植物 |
| P (部分正确) | 非答案但共享成分 | 排除该植物,该位置排除所有与猜测植物无共享成分的植物 |
| W (完全错误) | 完全无关 | 排除该植物,该位置排除所有与猜测植物有共享成分的植物 |
3.2.1 域更新的数学表达
设第 r r r 轮猜测为 G ( r ) \boldsymbol{G}^{(r)} G(r),反馈为 F ( r ) \boldsymbol{F}^{(r)} F(r)。对于位置 i i i 和植物 P k P_k Pk,域更新规则可形式化为:
D ( r + 1 ) ( X i ) = D ( r ) ( X i ) ∩ C i ( r ) D^{(r+1)}(X_i) = D^{(r)}(X_i) \cap \mathcal{C}_i^{(r)} D(r+1)(Xi)=D(r)(Xi)∩Ci(r)
其中 C i ( r ) \mathcal{C}_i^{(r)} Ci(r) 是第 r r r 轮反馈对位置 i i i 施加的约束集合。例如,若 F i ( r ) = W F_i^{(r)} = W Fi(r)=W 且 G i ( r ) = P j G_i^{(r)} = P_j Gi(r)=Pj,则:
C i ( r ) = { P k : sharesNone ( P k , P j ) } \mathcal{C}_i^{(r)} = \{P_k: \text{sharesNone}(P_k, P_j)\} Ci(r)={Pk:sharesNone(Pk,Pj)}
3.2.2 全局约束传播
约束传播后,算法执行以下全局操作:
-
全局排除 :设被排除的植物集合为 E \mathcal{E} E,则:
∀ i , D ( X i ) ← D ( X i ) ∖ E \forall i, D(X_i) \leftarrow D(X_i) \setminus \mathcal{E} ∀i,D(Xi)←D(Xi)∖E
-
互异性强制执行 :对于每个位置 i i i 和候选植物 P k ∈ D ( X i ) P_k \in D(X_i) Pk∈D(Xi),若存在某个其他位置 j ≠ i j \neq i j=i 使得 D ( X j ) ⊆ { P k } D(X_j) \subseteq \{P_k\} D(Xj)⊆{Pk}[2](#2),则 P k P_k Pk 必须从 D ( X i ) D(X_i) D(Xi) 中移除。形式化地,定义可行性函数:
canComplete ( i , P k ) ⟺ ∃ 互异的 ( p 0 , ... , p 3 ) 使得 p i = P k ∧ ∀ j , p j ∈ D ( X j ) \text{canComplete}(i, P_k) \iff \exists \text{ 互异的 } (p_0, \dots, p_3) \text{ 使得 } p_i = P_k \land \forall j, p_j \in D(X_j) canComplete(i,Pk)⟺∃ 互异的 (p0,...,p3) 使得 pi=Pk∧∀j,pj∈D(Xj)
则域更新为:
D ( X i ) ← { P k ∈ D ( X i ) : canComplete ( i , P k ) } D(X_i) \leftarrow \{P_k \in D(X_i) : \text{canComplete}(i, P_k)\} D(Xi)←{Pk∈D(Xi):canComplete(i,Pk)}
-
答案集合推理 :设已知答案植物集合为 A \mathcal{A} A。若 ∣ A ∣ = 4 |\mathcal{A}| = 4 ∣A∣=4,则:
∀ i , D ( X i ) ← D ( X i ) ∩ A \forall i, D(X_i) \leftarrow D(X_i) \cap \mathcal{A} ∀i,D(Xi)←D(Xi)∩A
若 ∣ A ∣ = 3 |\mathcal{A}| = 3 ∣A∣=3,则第 4 种植物必须从剩余候选中推断,设未被排除且不在 A \mathcal{A} A 中的植物集合为 U \mathcal{U} U,则:
∀ i (未锁定) , D ( X i ) ← D ( X i ) ∩ ( A ∪ U ) \forall i \text{ (未锁定)}, D(X_i) \leftarrow D(X_i) \cap (\mathcal{A} \cup \mathcal{U}) ∀i (未锁定),D(Xi)←D(Xi)∩(A∪U)
通过这种传播机制,每轮反馈后解空间会被大幅缩小。设第 r r r 轮后的解空间大小为 ∣ S r ∣ |\mathcal{S}_r| ∣Sr∣,则有:
∣ S r + 1 ∣ ≤ ∏ i = 0 3 ∣ D ( r + 1 ) ( X i ) ∣ × 排列因子 |\mathcal{S}{r+1}| \leq \prod{i=0}^{3} |D^{(r+1)}(X_i)| \times \text{排列因子} ∣Sr+1∣≤i=0∏3∣D(r+1)(Xi)∣×排列因子
其中排列因子由互异性约束决定,通常远小于初始值。
3.2.3 反馈模拟函数
在 Minimax 搜索中,需要模拟给定猜测 G \boldsymbol{G} G 对答案 A \boldsymbol{A} A 的反馈。定义反馈函数 Φ : P 4 × P 4 → { C , S , P , W } 4 \Phi: \mathcal{P}^4 \times \mathcal{P}^4 \to \{C, S, P, W\}^4 Φ:P4×P4→{C,S,P,W}4:
Φ ( G , A ) i = { C , if G i = A i S , if G i ≠ A i ∧ G i ∈ { A 0 , A 1 , A 2 , A 3 } P , if G i ∉ { A 0 , A 1 , A 2 , A 3 } ∧ sharesAny ( G i , A i ) W , if G i ∉ { A 0 , A 1 , A 2 , A 3 } ∧ sharesNone ( G i , A i ) \Phi(\boldsymbol{G}, \boldsymbol{A})_i = \begin{cases} C, & \text{if } G_i = A_i \\ S, & \text{if } G_i \neq A_i \land G_i \in \{A_0, A_1, A_2, A_3\} \\ P, & \text{if } G_i \notin \{A_0, A_1, A_2, A_3\} \land \text{sharesAny}(G_i, A_i) \\ W, & \text{if } G_i \notin \{A_0, A_1, A_2, A_3\} \land \text{sharesNone}(G_i, A_i) \end{cases} Φ(G,A)i=⎩ ⎨ ⎧C,S,P,W,if Gi=Aiif Gi=Ai∧Gi∈{A0,A1,A2,A3}if Gi∈/{A0,A1,A2,A3}∧sharesAny(Gi,Ai)if Gi∈/{A0,A1,A2,A3}∧sharesNone(Gi,Ai)
该函数用于在搜索过程中预测每种猜测的反馈分布。
3.3 Minimax 搜索策略
在缩小解空间后,如何选择下一轮的猜测?简单的选择是枚举当前所有可行解并逐一测试,但这种方法在最坏情况下可能效率低下。为了最小化最坏情况下的剩余候选数,我们采用 Minimax 搜索策略。
3.3.1 算法原理
Minimax 搜索的目标是找到一个猜测,使得在所有可能的反馈结果中,剩余候选解的最大数量最小化。具体步骤如下:
- 生成候选猜测 :从当前所有可行解中生成候选猜测集合,同时补充部分非可行解[3](#3)以增加搜索空间。
- 模拟反馈:对每个候选猜测,枚举所有可能的答案,计算模拟反馈。
- 划分统计:根据模拟反馈将答案集合划分为若干子集,统计每个子集的大小。
- 最坏情况评估:对每个候选猜测,取所有子集中最大的大小作为其最坏情况值。
- 选择最优:选择最坏情况值最小的候选猜测作为下一轮猜测。
3.3.2 搜索优化
Minimax 搜索的复杂度与候选猜测数量和答案集合大小成正比。为了提高效率,我们采用以下优化:
- 候选猜测去重:去除历史中已使用过的猜测,避免冗余。
- 答案集合上限 :在枚举可行解时设置上限[4](#4),防止解空间过大时搜索过慢。
- 早期终止 :若找到最坏情况值为 1 的猜测[5](#5),立即终止搜索。
通过这些优化,Minimax 搜索能在合理时间内选出最优猜测,确保在剩余轮数内完成求解。
3.4 算法流程
是
否
是
否
是
否
开始游戏
第 1 轮: 固定猜测 P2, P5, P9, P11
获取反馈
是否全 C?
求解完成
约束传播: 更新各位置域
枚举当前可行解
解数 = 1?
直接返回该解
Minimax 搜索: 选择最优猜测
提交猜测并获取反馈
是否全 C?
3.4.1 第 1 轮:成分覆盖
与旧算法相同,第 1 轮采用固定猜测 {P2, P5, P9, P11},分别对应太阳能豌豆 (A, B)、豌豆迫击炮 (B, C)、栗子投手 (C, D) 和荔枝 (D, E)。这 4 种植物的成分组合覆盖了全部 5 种 1 层成分 (A ~ E),确保首轮反馈能提供最全面的成分信息。
3.4.2 第2 ~ 5轮:CSP 约束传播 + Minimax 搜索
从第 2 轮开始,算法进入 CSP 求解循环:
- 根据上一轮反馈执行约束传播,更新各位置的域。
- 回溯搜索枚举所有满足当前约束的可行解。
- 若可行解唯一,直接返回该解作为猜测。
- 否则,使用 Minimax 搜索选择最优猜测,提交并获取新反馈。
- 重复上述过程,直至找到正确答案。
4 实现细节
4.1 数据结构设计
4.1.1 Plant 类
Plant 类定义了12种2层植物的基本信息:
- id:植物唯一标识 (1 ~ 12)。
- name:植物名称。
- e1, e2:两个 1 层成分索引 (A=0, B=1, C=2, D=3, E=4)。
- mask:成分位掩码,用于快速计算共享成分关系。
java
// 成分共享判断
public boolean sharesAny(Plant other) { return (mask & other.mask) != 0; }
public boolean sharesNone(Plant other) { return (mask & other.mask) == 0; }
位掩码运算使得成分共享判断的时间复杂度为 O ( 1 ) O(1) O(1),大幅提升了约束传播的效率。
4.1.2 Feedback 枚举
定义了四种反馈类型:
- CORRECT:植物及位置均正确。
- SWAP:植物正确但位置错误。
- PARTIAL:植物不正确但与答案植物共享成分。
- WRONG:植物完全错误且与答案植物无共享成分。
4.1.3 CSPSolver 核心状态
CSPSolver 类维护以下核心状态:
- domains :
Set<Integer>[],每位置的可能植物集合。 - answerPlants:已知答案植物集合 (来自 S/C 反馈)。
- excludedPlants:已排除植物集合 (来自 P/W 反馈)。
- fixedSlots:C 反馈锁定的位置映射。
- history:历史猜测集合,避免重复猜测。
4.2 约束传播实现
约束传播在 applyFeedback 方法中实现。每收到一组反馈,算法首先遍历4个位置,根据反馈类型更新对应位置的域和全局集合:
java
switch (fb) {
case CORRECT:
fixedSlots.put(slot, plantId);
answerPlants.add(plantId);
domains[slot].clear();
domains[slot].add(plantId);
break;
case SWAP:
answerPlants.add(plantId);
domains[slot].remove(plantId);
break;
case PARTIAL:
excludedPlants.add(plantId);
domains[slot].removeIf(pid -> Plant.get(pid).sharesNone(guessed));
break;
case WRONG:
excludedPlants.add(plantId);
domains[slot].removeIf(pid -> Plant.get(pid).sharesAny(guessed));
break;
}
随后执行全局排除和互异性强制执行,确保所有约束的一致性。
4.3 Minimax 搜索实现
Minimax 搜索在 findMinimaxGuessFull 方法中实现。算法首先生成所有候选猜测[6](#6),然后对每个候选计算最坏情况值:
java
private int computeWorstCase(int[] guess, List<int[]> solutions) {
Map<String, Integer> partitionSizes = new HashMap<>();
for (int[] answer : solutions) {
String fb = simulateFeedback(guess, answer);
partitionSizes.put(fb, partitionSizes.getOrDefault(fb, 0) + 1);
}
int max = 0;
for (int size : partitionSizes.values()) if (size > max) max = size;
return max;
}
通过遍历所有候选猜测并选择最坏情况值最小的作为下一轮猜测,算法确保每次猜测都能最大程度缩小解空间。
5 测试结果
为了验证 CSP 求解器的性能,我们编写了全量测试程序 TestRunner,遍历所有 C 12 4 ⋅ 4 ! = 11880 C_{12}^4\cdot4!= 11880 C124⋅4!=11880 种可能的答案,统计求解轮数。
5.1 测试数据
| 指标 | 数值 |
|---|---|
| 总测试用例 | 11880 |
| 5 轮内求解 | 11880 (100%) |
| 平均求解轮数 | ~3.5 |
| 最大求解轮数 | 5 |
5.2 结果分析
CSP 求解器实现了 100% 金奖杯率,所有用例均在 5 轮内求解,平均仅需约 3.5 轮。相比旧版算法,新版在困难用例上的表现有显著提升。
6 可视化界面
同旧版本,略。
7 小结
通过将解码小游戏建模为约束满足问题,并结合约束传播与 Minimax 搜索,我们成功设计了一种 100% 金奖杯率的求解算法。该算法在平均 3.5 轮内即可求解所有 11880 种可能答案,远超旧版算法的性能。
本文代码参见 Gitee 仓库 的 csp 模块,发行版可直接通过 发行版仓库 下载。