[P] 结对项目:花见小路

[P] 结对项目:花见小路

项目 内容
这个作业属于哪个课程 首页 - 2026年春季软件工程 - 北京航空航天大学 - 班级博客 - 博客园
这个作业的需求在哪里 [P] 结对项目:花见小路 - 作业 - 2026年春季软件工程 - 班级博客 - 博客园
我在这个课程的目标是 通过学习理论和进行实践,熟悉软件开发的全流程,提升自己的学习能力和丰富开发经验
这个作业在哪个具体方面帮助我实现目标 学习了解结对编程相关知识,进行结对编程实践

→ 📖 Q0.0(P) 如果你的代码仓库包含 AIGC 的部分,列举使用的工具、模型和使用范围。若未使用则填写:本组提交的全部代码不包含AI补全或生成的部分。

本项目完整采用了 AI 辅助编程(Vibe Coding)范式。使用的主要工具为 antigravity,底层频繁切换调用的模型为 Claude Opus 4.6 以及 Gemini 3.1 Pro。使用范围涵盖了 T1、T2、T3 的基础逻辑代码生成、Rust 语法及所有权报错的快速修复、以及辅助构建海量的边缘测试用例脚本。不过,核心的博弈算法设计(PIMC+Alpha-Beta剪枝)与底层的系统状态机架构,是我们两人线下结对讨论推演出来的,随后才将其转化为 Prompt 引导大模型去生成具体代码。

Chapter.0 wasm从安装到入门

→ 📖 Q0.1(P) 请记录下目前的时间。

2026年3月29日 10:00

→ 📖 Q0.2(I) 作为本项目的调查:

对 Wasm 的熟悉程度:I. 没有听说过。

对桌游花见小路的熟悉程度:I. 不了解玩法和规则。

→ 📖 Q0.3(P) 请记录下目前的时间。

2026年3月29日 10:45

Chapter.1 七色之缨

→ 📖 Q1.1(P) 请记录下目前的时间。

2026年3月29日 12:30

→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 110 115
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 15 10
- Technical Background - 了解技术背景(包括学习新技术) 20 25
- Coding Standard - 代码规范 5 5
- Design - 具体设计(确定怎么实现) 10 10
- Coding - 具体编码 30 30
- Code Review - 代码复审 10 10
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景) 10 15
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 10 10
REPORTING 报告 15 15
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 5 5
- Size Measurement - 计算工作量 5 5
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 5 5
TOTAL 合计 130 135

查阅资料:学习 wasm-bindgen 数组传参规范(最终采用 &[i8]),并明确花见小路的胜负及第三回合复杂的连环决胜规则。
逻辑设计:摒弃复杂数据结构,将一维数组直接降维映射为 4 个核心状态量(双方的分数与标记物)。按“绝对胜利 -> 回合流转 -> 终局决胜”的优先级顺序,设计防御性判定分支。
测试设计:借助大模型生成辅助测试脚本,重点覆盖“高分与多标记物同时触发”的优先级冲突,以及第三回合多区域同分的极限平局场景。
问题与解决:第一个是Wasm/Rust 类型报错,我们对所有权和内存映射不熟。将报错交由 AI 分析,改为基础切片传参,第二个是决胜逻辑冗余,多区域判定嵌套过深,我们提取局部布尔变量合并条件,简化了代码。

→ 📖 Q1.3(P) 请说明你们为这个判定模块设计了哪些中间量或辅助函数;如果没有额外设计,也请说明为什么认为直接实现已经足够清晰。

hanamikoji_judge 函数中,我们没有拆分额外的辅助函数,而是通过设计几个关键的中间量来实现逻辑:

  • scores 数组 ([2, 2, 2, 3, 3, 4, 5]):将 7 个地标的权重进行了硬编码映射,避免了冗长的 switch/match 分支。
  • 状态统计变量 (my_score, opp_score, my_tokens, opp_tokens):通过一次对 board 的 $O(N)$ 遍历,将当前盘面抽象为分数和标记物数量这四个核心维度。

没有额外设计辅助函数的原因
判定逻辑虽然涉及多重条件(常规获胜、回合流转、第三回合平局判定),但其本质是一个纯粹的数据流判断,不包含复杂的嵌套循环或状态突变。整体代码行数控制在 60 行左右,线性向下执行的逻辑(计算特征 -> 判定绝对获胜 -> 判定非末回合 -> 判定第三回合 Tie-breakers)非常契合阅读直觉。强行抽离成多个辅助函数反而会导致上下文的频繁传递,增加阅读负担。

→ 📖 Q1.4(I) 请说明在这样一个规则判定类模块中,如何避免“漏判”“错判”或分支顺序错误等问题。

个人认为可以从设计测试两个阶段来有效防范这类问题。

具体而言:

  • 在设计阶段:动手写代码前,先画出完整的规则判定流程图,理清所有胜利、平局条件的优先级与分支。在实际编码时,严格对照流程图,利用“卫语句(提前 return)”按优先级逐层拦截分支,避免逻辑纠缠。
  • 在测试阶段:针对流程图中的每一个判定分支,都构造对应的测试用例,特别是刚好达标、多条件冲突等“边界情况”。结合分支覆盖率测试工具,确保所有代码路径符合预期。如果某些极端样例始终无法通过,就需要回溯检查是否在流程设计之初就出现了错判或漏判。

→ 📖 Q1.5(P) 请说明你们设计了哪些测试用例,这些测试分别覆盖了哪一类规则或边界情况。

我们设计的测试用例主要覆盖了以下几类核心规则与边界情况:

  • 分数获胜测试:测试玩家或对手单方面 score >= 11 的情况(无论回合数)。
  • 标记物获胜测试:测试玩家或对手获得 4 个标记物(tokens >= 4)且对方分数不足 11 分的情况。
  • 回合流转测试:模拟第 1、2 回合结束时,双方均未达到获胜条件,验证系统正确返回 0(游戏继续)。
  • 第三回合连环 Tie-breaker 测试
    • 覆盖常规平局:双方分数、标记物均不达标,但一方分数较高。
    • 覆盖 G (index 6) / F (index 5) 决胜点:构造分数和标记完全相同的盘面,仅改变 6 号位或 5 号位的归属。
    • 覆盖多位点决胜(D/E 与 A/B/C):验证 me_de && opp_de 同时存在时返回 2(彻底平局),以及单一归属时正确返回 1-1
  • 互斥胜利边界测试:例如一方标记物大于 4,但另一方分数大于 11,测试系统是否按照规则优先判定分数高者/或同时满足时的特殊处理。

→ 📖 Q1.6(I) 请说明你对“先写测试再实现”与“先实现再补测试”两种方式的理解。

个人认为可以从思维模式与适用场景两个角度来理解这两种开发方式。 具体而言:

在先写测试再实现时:相当于先定下验收标准再开工。开发者必须提前思考接口设计与边界条件,这能有效倒逼出低耦合、高质量的代码结构,非常适合复杂逻辑或核心规则模块的开发。

在先实现再补测试时:相当于先把大楼盖起再做质检。这种方式初期阻力小、思路连贯,能快速验证功能原型,适合需求模糊或频繁变动的探索期项目,但后期补充测试时容易产生思维盲区,只针对既有代码测而忽略了真正的边界用例。

→ 📖 Q1.7(P) 请记录下目前的时间,并根据实际情况填写附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。

2026年3月29日 14:45

→ 📖 Q1.8(I) 请写下本部分的心得体会。

本次作业是我第一次对 Rust 有好好地了解,此前对编程语言的认知一直只停留在比较宽松的程度。它的严谨虽然一开始让人有些头疼,但也实打实地帮我拦住了很多低级错误,让人写完后觉得很安心。在和同伴一起梳理游戏规则时,我们经过讨论,互相补充对于平局判定理解有遗漏的地方,最后完成编程,一同考虑测试样例的编写。有个同伴一起帮忙找逻辑漏洞,自然是比自己闷着头写好得多的,我想这个也是这次实践的一大乐趣与收获。

Chapter.2 不祥之影

→ 📖 Q2.1(P) 请记录下目前的时间。

2026年3月29日 14:50

→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 45 35
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 5 5
- Technical Background - 了解技术背景(包括学习新技术) 5 5
- Coding Standard - 代码规范 5 2
- Design - 具体设计(确定怎么实现) 10 8
- Coding - 具体编码 10 10
- Code Review - 代码复审 5 2
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 3 2
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 2 1
REPORTING 报告 10 10
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 3 3
- Size Measurement - 计算工作量 2 2
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 5 5
TOTAL 合计 60 50

技术准备:为避免 JSON 序列化开销,决定引入 js_sys::Int32Array,将双方手牌与盘面状态“拍平”为长度 21 的一维数组返回 JS。
逻辑设计:按回合解析 history,模拟四种动作(1记录、2舍弃、3/4处理复杂的选牌分配),结算时比较卡牌数,相等则继承原 board 归属。
测试设计:覆盖了单动作验证、8 动作全流程连贯推演,以及特定的“平局状态继承”边界测试。
遇阻与解决:Rust 的字符解析与索引转换易引发越界,且 Action 4 的选牌剔除较繁琐,我们枯燥的解析交由大模型,利用其生成的安全迭代器和布尔数组(used_in_chosen)完美规避了 Panic 和匹配错漏。

→ 📖 Q2.3(P) 请说明针对该任务,你们对 T1 中已实现的代码进行了哪些复用和修改。

在这个模块中,我们主要复用了 T1 中关于状态抽象的设计思想:即将花见小路复杂的盘面降维成数组索引(0-6 对应 A-G)。代码层面,我们复用了 T1 中对于 board: &[i8] 的 Wasm 传参模式。同时,T1 中“比较数值大小判定地标归属(1 为我方,-1 为对手)”的核心规则被复用到 new_board 的生成逻辑中:如果 p1_cards[i] > p2_cards[i] 则置 1,反之置 -1,相等则回退使用旧的 board[i]。区别在于,T1 是基于已知结果进行最终裁判,而 T2 是通过历史记录去推演并生成这个结果。

→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。

在架构解耦上:采取“分离关注点”的设计思想,将字符串解析与核心规则引擎拆分为独立模块,这在构建复杂的软件系统或游戏底层逻辑时,能极大降低未来指令集变更带来的连锁反应。

在数据抽象上:利用结构体与枚举构建强类型的领域模型来替代硬编码的定长数组,为后续可能新增的卡牌种类或计分机制预留充足的架构设计冗余。

→ 📖 Q2.5(P) 头脑风暴环节:我们终于快要开始让程序玩游戏了!请尝试分析:T2 中不带 X 的操作记录比起实际对局多出了多少信息?如果加上 X,也就是失去了这部分信息的话,如何处理对小轮结束后状态的估计?

T2 的 history 是一份“上帝视角”的完美信息记录。在实际的桌游对局中,玩家的 Action 1(密印/Secret)和 Action 2(弃牌/Discard)都是背面朝下的暗牌。此时的系统通过历史记录如 1C2DE 确切知道了这两张牌是什么,这比起实际对局多出了对手隐藏手牌库与弃牌堆的绝对信息。一旦隐藏动作变为 1X2XX,游戏就从“完美信息博弈”变成了“非完美信息博弈”,对于此问题,处理方法如下:

  1. 剩余卡牌池:由于每种卡牌的总量是固定的(2,2,2,3,3,4,5),我们可以用总卡牌减去“自己手中的牌”和“场上已明示的牌”,得出当前所有未知卡牌的集合。
  2. 概率分布估计:对手的 X 必然从未知卡牌集合中产生。我们可以通过计算每种卡牌在未知集合中的比例,将其转化为一个概率矩阵,估计对手某张暗牌是 A-G 的概率。

→ 📖 Q2.6(P) 请记录下目前的时间,并根据实际情况填写 附录 A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。

2026年3月29日 15:40

→ 📖 Q2.7(I) 请写下本部分的心得体会。

借助 AI 自动化处理 Rust 复杂的生命周期与跨语言交互细节,我们成功规避了底层实现的繁琐心智消耗。

这使团队得以专注于“花见小路”核心规则的逻辑推演,充分验证了“AI 写代码,人类定框架”模式的高效性。

Chapter.3 道途之荆

→ 📖 Q3.1(P) 请记录下目前的时间。

2026年4月3日 13:00

→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 20 15
- Estimate - 估计这个任务需要多少时间 20 15
DEVELOPMENT 开发 240 280
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 40 50
- Technical Background - 了解技术背景(包括学习新技术) 20 30
- Coding Standard - 代码规范 10 10
- Design - 具体设计(确定怎么实现) 40 45
- Coding - 具体编码 80 90
- Code Review - 代码复审 20 25
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景) 20 15
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 10 15
REPORTING 报告 40 50
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 15 20
- Size Measurement - 计算工作量 5 5
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 20 25
TOTAL 合计 300 345

完成编程任务期间的过程记录:

技术准备:引入 js_sys::Date::now 满足 2 秒限时,并手写轻量级 Xorshift64 伪随机算法以严格控制 Wasm 产物大小。
逻辑设计:构建基于 PIMC 的采样架构,结合 Alpha-Beta 剪枝树,以及综合“地标控制权、分数、卡牌期望值”的启发式评估函数,实现核心决策逻辑。
测试设计:通过本地自我博弈,验证AI第一步的直觉合理性,并针对 Action 4 构造极限分支压测,通过同学之间互相博弈,在实战中对程序进行测试。
遇阻与解决:Action 4 合法分牌组合爆炸易引发超时,借助 AI 生成了栈内存级别的极速去重分牌函数,并辅以“1500ms 时限硬中断 + 动态搜索深度(3至5层)”的双重保险,彻底攻克性能瓶颈。

→ 📖 Q3.3(P) 头脑风暴环节:假设提供更充裕的时间和资源,这个游戏中你能找到的最优策略有可能是什么形式的?

花见小路是一个典型的非完美信息、零和、双人博弈。我们目前采用的 PIMC完美信息蒙特卡洛存在理论缺陷,即“策略融合”与“非局部性”问题,它假设对手在每个采样世界中都能看到我们的暗牌并做出针对性最优解,这会导致 AI 过于保守。

如果时间和算力极其充裕,最优策略的形态应该是:

  1. 反事实遗憾最小化:这是目前解决非完美信息博弈的数学最优解形态。通过不断自我博弈迭代,最终收敛到一个纳什均衡策略。形式上,它会表现为一个庞大的查找表或神经网络,对于任何一个信息集,直接输出各种合法动作的概率分布,而不是像现在这样每次都在线搜索。
  2. Deep Reinforcement Learning:训练一个价值网络和策略网络,结合隐藏状态的信念追踪,将历史记录编码为隐状态向量输入给模型。

→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么选择行动类型?选牌如何更优?如何编程实现。

针对苛刻的 2 秒时间限制和复杂的博弈树,我们在编程实现时采取了以下针对性优化策略:

  1. 时限硬中断与动态深度
    设定 time_budget = 1500.0 毫秒,预留 500ms 的通信与序列化缓冲区。在最外层的 loop 中,如果当前耗时超过预算,立即中断 PIMC 采样并返回当前最优解。同时,根据当前可选动作的数量动态调整 Alpha-Beta 搜索树的深度,分支少则搜 5 层,分支多如刚开局时只搜 3 层,确保单次采样的耗时可控。
  2. 动作排序优化
    为了让 Alpha-Beta 剪枝尽早发生,我们在 alphabeta 函数中实现了动作排序。在浅层搜索时,利用类别直接排序,Action 1 > Action 4 > Action 3 > Action 2,因为隐藏/保留高价值牌通常是好动作;在顶层或深层搜索时,先用启发式函数对第一层子节点进行一次廉价评估并降序排列,这提升了剪枝效率。
  3. 启发式评估
    在选牌优化上,我们没有单纯计算胜负,而是在 evaluate_heuristic 中引入了期望值计算。程序会判断某张牌的数量是否过半,如果处于拉锯状态,则计算双方手中与牌库中该花色卡牌的期望差值(diff = e1 - e2)。这种平滑的打分机制让 AI 倾向于抢夺权重高(如 5分、4分)且当前处于优势的位点。

→ 📖 Q3.5(P) 请说明针对该任务,你们对 T2 中已实现的代码进行了哪些复用和修改。

T3 的核心解析逻辑完全脱胎于 T2。

  • 复用:我们复用了 T2 中逐个按 split('-') 和字符遍历解析 history 字符串的基础骨架。判定当前操作者(i % 2 == 0)以及识别动作类型('1'到'4')的思路被完整继承。
  • 修改与升级:在 T2 中,我们的目标仅仅是还原“公开的完美盘面”,因此对 'X' 直接采取了丢弃/忽略处理。而在 T3 中,为了支持后续的搜索算法,我们将解析函数升级为了 parse_history,其最大的修改是加入了隐蔽信息追踪:遍历卡牌和历史记录时,维护了一个 hidden_pool。凡是遇到已知的卡牌,就从 hidden_pool 中剔除,从而精准计算出当前未知的卡牌集合,为 PIMC 的 determinize(采样确定化)提供了数据基础。此外,我们将松散的数组封装成了严密的 GameNode 状态机。

→ 📖 Q3.6(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。

配置与逻辑解耦:提取 SCORE_BY_ID 等全局常量,使规则变更(如扩展包)无需改动底层逻辑,提升了系统的扩展性。

纯函数式状态机:将 apply_move 设计为自包含的状态转移函数,为并发搜索以及未来接入 MCTS(蒙特卡洛树搜索) 奠定了架构基础。

双重时限冗余:通过“系统时钟”与“迭代次数”双重校验控制循环退出,确保程序在不同硬件性能下都能自适应防止死锁并按时返回

→ 📖 Q3.7(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块决策能力很强?”,尝试提出一些可能的定量分析方式或测试方式。

image-20260408173946561

如图所示,我们通过与其他的组的同学进行对局对抗进行测试,实战是最有说服力的指标,如果我们的 AI 能在与他人的程序对局下保持高胜率,且未发生崩溃、超时或输出非法动作,即可证明其有效性。

可能的定量分析方式

  1. ELO 等级分系统:编写一个本地的测试脚本,让 T3 的当前版本与旧版本,或纯随机/贪心的简单策略,进行数千局的自动对局。计算其 ELO 积分,通过积分的稳步上升来定量证明算法优化的有效性。
  2. 残局解题率测试:在花见小路社区收集特定的“一招制敌”或“绝境逢生”的残局历史字符串。将这些固定局面喂给 AI,统计它能在多少毫秒内找到理论最优解的命中率。

→ 📖 Q3.8(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。

2026年4月3日 18:45

→ 📖 Q3.9(I) 请写下本部分的心得体会。

人类负责博弈架构与权重的顶层设计,AI 则承接 Rust 底层算法实现与内存优化,确保了在 2 秒限制内完成高频采样。

这一协作模式弥合了“博弈理论”到“高性能代码”间的鸿沟,证明了未来开发的核心壁垒在于系统建模,而非掌握语言的语法细节。

结对项目总结

→ 📖 Q4.1(P) 提供两人在讨论的结对图像资料。

9f24af3d37c7e427624c1ca93d9b469b

→ 📖 Q4.2(P) 回顾结对的过程,反思有哪些可以提升和改进的地方。

在本次引入了 AI 辅助编程的结对过程中,我们发现传统的结对模式发生了变化,也暴露出了一些需要改进的点:

  1. AI 提示词的协同构建:初期我们常常是一个人构思好逻辑直接去问 AI,另一个人只能看着,后来我们意识到在 Vibe Coding 中,结对的重心应前移。两人应该先在白板或草稿纸上结对画出状态机和数据流,共同拟定好 Prompt 的约束条件,然后再交由一人去“驾驶”大模型生成代码。
  2. 对 AI 生成代码的审查盲区:因为大模型生成代码速度极快,有时我们会产生惰性,直接 Copy-Paste 运行,导致一些隐蔽的边界 Bug(如越界或生命周期问题)在测试阶段才暴露,对于结对编程,其中一人应当严格落实“导航者”的职责——一人负责引导大模型生成并集成代码,另一人必须逐行审查大模型产出的核心逻辑,充当“人类编译器”和“逻辑审阅者”。

→ 📖 Q4.3(I) 锐评一下你的搭档!并请至少列出三个优点和一个缺点。

本次结对作业中,我的搭档是李昊霖

优点

耐心,善于沟通、学习能力强

缺点

项目过程中需要提醒完成

→ 📖 Q4.4(I) 说明结对编程的优缺点、你对结对编程的理解。

我对结对编程的理解是:两名开发者共享一台工作站,一人专注于编写代码(“驾驶员”),另一人则负责实时审查代码并思考全局架构(“领航员”),两人在此过程中频繁互换角色。

优点: 能够通过持续的实时代码审查显著减少Bug,提升整体代码质量;同时能极大地促进团队内部的知识共享与技术传递,降低项目风险。

缺点: 需要投入双倍的人力资源来完成同一项任务,初期看起来会降低开发速度;此外,如果两人沟通不畅、性格不合或编码习惯差异过大,很容易产生摩擦并加剧疲劳感。

→ 📖 Q4.5(P) 请提供你们完成代码实现的代码仓库链接。

https://github.com/paidaxingKing/BUAASE2026-PairProgramming.git

posted @ 2026-04-09 12:34  π大星314  阅读(53)  评论(0)    收藏  举报