[P] 结对项目:花见小路
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026年春季软件工程 (北京航空航天大学 - 计算机学院) |
| 这个作业的要求在哪里 | [P] 结对项目:花见小路 |
| 我在这个课程的目标是 | 了解现代软件工程的思想和开发方法 |
| 这个作业在哪个具体方面帮助我实现目标 | 体会结对编程,学会结合沟通与开发 |
结对项目:博客问题清单
→ 📖 Q0.0(P) 【你可以在结对结束后补充】如果你的代码仓库包含 AIGC 的部分,列举使用的工具、模型和使用范围。若未使用则填写:本组提交的全部代码不包含AI补全或生成的部分。
使用 codex 实现了一个用于测评的随机出牌算法,并搭建了能够随机生成牌局的测评程序;最后一版的程序实现,是我们在已有代码的基础上,通过将优化方向告知 codex,让其生成的一版改进版。
Chapter.0 wasm从安装到入门
引入
→ 📖 Q0.1(P) 请记录下目前的时间。
2026年4月7日 20:27
调查
→ 📖 Q0.2(I) 【你可以在结对结束后另行补充。】作为本项目的调查:
请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
I. 没有听说过;
II. 仅限于听说过相关名词;
III. 听说过,且有一定了解;
IV. 听说过,且使用 Wasm 实际进行过开发(即便是玩具项目的开发)。
答:I. 没有听说过
请如实标注在开始项目之前对桌游花见小路的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
I. 不了解玩法和规则;
II. 听说过,且有一定了解;
答:I. 不了解玩法和规则
总结
→ 📖 Q0.3(P) 请记录下目前的时间。
2026年4月7日 20:47
Chapter.1 七色之缨
结对过程
→ 📖 Q1.1(P) 请记录下目前的时间。
2026年4月7日 20:47
→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
任务耗时估计
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | 5 | 3 |
| - Estimate | - 估计这个任务需要多少时间 | 5 | 3 |
| DEVELOPMENT | 开发 | 130 | 94 |
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 4 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 30 | 20 |
| - Coding Standard | - 代码规范 | 10 | 5 |
| - Design | - 具体设计(确定怎么实现) | 10 | 10 |
| - Coding | - 具体编码 | 20 | 30 |
| - Code Review | - 代码复审 | 10 | 10 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 20 | 10 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 20 | 5 |
| REPORTING | 报告 | 30 | 30 |
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 10 |
| - Size Measurement | - 计算工作量 | 10 | 10 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 10 | 10 |
| TOTAL | 合计 | 165 | 127 |
完成任务期间依次做了什么:
- 阅读题目要求,了解具体设计需求。
- 学习Rust基础语法,教程参考了rust语言圣经。
- 设计结算的判定逻辑。
- 具体编码实现。
- 设计测试用例,使用工具链编译,然后使用课题组提供的test.js进行测试。
- 总结与反思,回答后续的问题。
设计
→ 📖 Q1.3(P) 请说明你们为这个判定模块设计了哪些中间量或辅助函数;如果没有额外设计,也请说明为什么认为直接实现已经足够清晰。
辅助函数:
-
fn get_score(board: &[i8], player: i8) -> i32- 作用:获取某个玩家总共获得的分数
- 输入:表示倾心状态的数组切片board,玩家player
- 输出:玩家player获得的分数
-
fn get_count(board: &[i8], player: i8) -> i32- 作用:获取某个玩家总共获得的标记数
- 输入:表示倾心状态的数组切片board,玩家player
- 输出:玩家player获得的标记数
中间变量:
- my_s:自己的分数
- op_s:对手的分数
- my_c:自己的标记数
- op_c:对手的标记数
- my_has_de:自己是否拥有DE等级的标记的标示
- op_has_de:对手是否拥有DE等级的标记的标示
- my_has_abc:自己是否拥有ABC等级的标记的标示
- op_has_abc:对手是否拥有ABC等级的标记的标示
→ 📖 Q1.4(I) 请说明在这样一个规则判定类模块中,如何避免“漏判”“错判”或分支顺序错误等问题。
- 根据题目给定的规则,了解判定的优先级顺序,在编码时严格按顺序执行。
- 在实现后,可以进行分支覆盖测试,检查是否有遗漏的分支。
测试
→ 📖 Q1.5(P) 请说明你们设计了哪些测试用例,这些测试分别覆盖了哪一类规则或边界情况。
我们设计的测试用例如下:
- 一方分值达到 11 分而获胜
- 输入:board=[0, 0, 0, 0, -1, -1, -1],round=2
- 输出:-1
- 一方获得至少 4 枚倾心标记而获胜
- 输入:board=[1, 1, 1, 1, 0, -1, 0],round=1
- 输出:1
- 前两小轮结束时尚未满足胜利条件,应返回 0
- 输入:board=[1, 1, 1, 0, 0, -1, 0],round=1
- 输出:0
- 第三小轮结束时,总分不同,由总分高者获胜
- 输入:board=[1, 1, 1, 0, 0, -1, 0],round=3
- 输出:1
- 第三小轮结束时,总分相同,由最高档位倾心标记判定胜负
- 输入:board=[1, 1, -1, 1, 0, 0, -1],round=3
- 输出:-1
- 第三小轮结束时平局,应返回 2
- 输入:board=[1, -1, 0, 0, 0, 0, 0],round=3
- 输出:2
→ 📖 Q1.6(I) 请说明你对“先写测试再实现”与“先实现再补测试”两种方式的理解。
- “先写测试再实现”本质是强调了在写代码前先想清楚“这个功能应该如何被使用”,这样我们在编码时更容易带有一种从高处审视的姿态,有助于了解边界条件。
- “先实现再补测试”的强调快速实现,对于规则简单、边界条件能一次性枚举的模块,这种方式效率较高,测试主要起验证作用。
总结
→ 📖 Q1.7(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026年4月7日 22:30
→ 📖 Q1.8(I) 请写下本部分的心得体会。
我们在实现 T1 时,主要的时间都用来熟悉 Rust 这门编程语言了,至于正式编程的时间倒不是很多。主要的体会是 Cargo 的功能实在强悍,Rust 的生态也很全面,并且 Rust 中比如match这样的特性就很适合完成对规则判定场景的实现。
Chapter.2 不祥之影
准备
→ 📖 Q2.1(P) 请记录下目前的时间。
2026年4月8日 15:30
→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
任务耗时估计
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | 3 | 3 |
| - Estimate | - 估计这个任务需要多少时间 | 3 | 3 |
| DEVELOPMENT | 开发 | 100 | 90 |
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 10 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 10 | 10 |
| - Coding Standard | - 代码规范 | 10 | 10 |
| - Design | - 具体设计(确定怎么实现) | 20 | 15 |
| - Coding | - 具体编码 | 20 | 20 |
| - Code Review | - 代码复审 | 10 | 5 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 10 | 10 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 10 | 10 |
| REPORTING | 报告 | 30 | 25 |
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 10 |
| - Size Measurement | - 计算工作量 | 10 | 5 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 10 | 10 |
| TOTAL | 合计 | 165 | 118 |
完成任务期间依次做了什么:
- 阅读题目要求,了解具体设计需求。
- 设计代码逻辑。
- 执行具体编码任务。
- 设计测试用例。
代码可复用性与需求变更
→ 📖 Q2.3(P) 请说明针对该任务,你们对 🧑💻 T1 中已实现的代码进行了哪些复用和修改。
T2的任务要求和T1重复度较低,我们没有使用T1中已经实现的代码,而是完全重新编写了代码。
→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
在 T2 中,我们尽可能的解耦了代码的职责,创建了多个功能函数,比如add_card()和process_action()。
虽然 T2 给我们的是完整的一个对局历史,但我们仍使用这些函数来处理单步的一次行动。
事实证明,在 T3 的场景中,这两个函数都仅经过了简单的修改便可以继续使用。
头脑风暴环节
→ 📖 Q2.5(P) 头脑风暴环节:
我们终于快要开始让程序玩游戏了!请尝试分析:T2 中不带 X 的操作记录比起实际对局多出了多少信息?如果加上 X,也就是失去了这部分信息的话,如何处理对小轮结束后状态的估计?
T2 比实际对局多出的信息:
实际对局中,玩家对对手的密约(1X)和取舍(2XX)的具体牌面是保密的。T2 中这些行动全部公开(如 1D、2GG),因此 T2 的输入比真实对局多出了以下两类信息:
- 对手密约的具体牌型(1 张)
- 对手取舍的具体牌型(2 张)
每小轮最多多出 3 张牌的信息(密约+取舍)。
如果有 X(信息不完整),如何估计结束后状态:
当存在 1X 和 2XX 时,我们无法直接还原双方的确切持牌数,但可以通过以下方式估计:
- 候选集推断:已知总牌堆(A×2, B×2, ..., G×5,共 21 张,减去暗置的 1 张 = 20 张),减去所有已知牌面,剩余即为未知牌的候选集。
- 枚举可能性:对
1X和2XX列出所有候选牌的组合,对每种组合计算可能的得分标记结果,取概率最高或期望最优的状态作为估计。 - 悲观决策:如果以最悲观的情况进行决策,可以对所有可能的 X 作最坏估计(假设对手密约了对我方最不利的牌),以此进行保守决策。
总结
→ 📖 Q2.6(P) 请记录下目前的时间,并根据实际情况填写 附录 A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026年4月8日 17:20
→ 📖 Q2.7(I) 请写下本部分的心得体会。
在完成 T2 的过程中我深刻体会到了解耦设计的好处,我和同伴在明确设计需求后,优先设计了一个全局框架。我们采用的是先搭建主函数,然后再实现具体的子函数的方法。事实证明,这个方法工作得很好,不仅理清了我们的思路,而且降低了编程的难度。此外,拜 Rust 的库方法所赐,本应该算一个难点的字符串处理部分也大大简化了。
Chapter.3 道途之荆
准备
→ 📖 Q3.1(P) 请记录下目前的时间。
2026年4月8日 18:30
→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
任务耗时估计
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | 3 | 3 |
| - Estimate | - 估计这个任务需要多少时间 | 3 | 3 |
| DEVELOPMENT | 开发 | 180 | 210 |
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 10 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 10 | 10 |
| - Coding Standard | - 代码规范 | 10 | 10 |
| - Design | - 具体设计(确定怎么实现) | 40 | 60 |
| - Coding | - 具体编码 | 60 | 70 |
| - Code Review | - 代码复审 | 10 | 20 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 20 | 10 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 20 | 20 |
| REPORTING | 报告 | 25 | 25 |
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 10 |
| - Size Measurement | - 计算工作量 | 5 | 5 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 10 | 10 |
| TOTAL | 合计 | 208 | 238 |
完成任务期间依次做了什么:
- 阅读题目要求,了解具体设计需求。
- 讨论要设计什么样的行动策略
- 解耦代码设计,探讨需要设计哪些功能的函数。
- 具体编码实现。
- 设计测试用例。
- 根据测试结果优化代码。
头脑风暴环节
→ 📖 Q3.3(P) 头脑风暴环节:
假设提供更充裕的时间和资源,这个游戏中你能找到的最优策略有可能是什么形式的?进行调研并总结分析。你还可以在任务结束后试着实现(不计分)。
调研后认为,花见小路属于不完美信息双人零和博弈,我们考虑的一种最优策略如下:
对每个可能的行动,通过大量随机对局模拟估计胜率,选择期望胜率最高的行动,我们调查后了解到这个算法被称为蒙特卡洛树搜索(MCTS)。
在此基础上,还可以通过以下辅助策略降低计算量并且提高胜率:
- 对相似的选择进行剪枝,降低计算量。
- 设计简单的打分函数,仅对高质量的动作进行深度模拟。
需求建模和算法设计
→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么选择行动类型?选牌如何更优?如何编程实现。
我们采用了基于状态重建的贪心决策逻辑,具体的思路如下:
- 状态重建:复用了T2的
calc_current_state()先把历史回放成一个GameState。其中my_cards记录我方已落场牌,op_cards[0..7)记录对手已知落场牌,op_cards[7]记录对手场上的未知牌数,known_removed/unknown_removed记录弃牌信息,used_actions记录我方已经用过的四种行动,need_response表示当前是否正处在响应别人赠予/竞争的回合。 - 牌面价值评估:
Evaluator为每个场景计算imp(对自己的重要度)和o_imp(对对手的重要度)。价值主要由三部分决定:牌本身的分值SCORES、当前board的归属状态、以及我方可控制总量与对手场上数量的差值。直观上,正在争夺、只差 0 或 1 张的艺伎权重最高;已经明显领先或明显落后的艺伎权重会下降。 - 行动类型选择:基于对场面剩余牌的分布,把当前所有合法的
1/2/3/4动作全生成出来,再逐个算期望分数。1偏向保留高价值牌,2偏向弃掉低价值牌,3会假设对手拿走其视角下最想要的那张牌,4会枚举所有 2+2 分组并比较我方保留与对手获得的价值差。 - 响应选择:如果当前需要响应别人的
3或4。评分时同时考虑“这张牌/这组牌对我有多重要”和“拿走它后对手少赚多少”,对应代码里的RESPONSE_ALPHA和RESPONSE_BETA。
代码可复用性与需求变更
→ 📖 Q3.5(P) 请说明针对该任务,你们对 🧑💻 T2 中已实现的代码进行了哪些复用和修改。
T3 没有直接调用 T2 的源码接口,但延续了 T2 的“按历史回放动作”思路,并在此基础上做了 T3 所需的改造。
- T2 处理的是“一整小轮已经结束”的完整公开历史,而 T3 处理的是“当前时刻的部分历史”。因此这里新增了
need_response、used_actions和pending_offer相关逻辑,用来判断当前究竟是该主动出牌还是该响应别人。 - T2 默认信息是完整的,T3 则必须面对
1X、2XX。因此新增了op_cards[7]、known_removed、unknown_removed等结构,把“隐藏信息推断”变成决策的一部分。 - T2 的目标是复原结束状态;T3 的目标是输出当前最优动作。所以在回放逻辑之外,又增加了
Evaluator、候选动作生成、行为打分和响应策略等模块。 - T2 中设计了针对最终局面进行结算的函数
settle(),但在 T3 中我们没有涉及模拟游戏直到结算的逻辑,因此 T3 中舍弃了这个函数。
→ 📖 Q3.6(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
在本次编码实现中,为了提高既有代码对需求变更的适应能力,我主要采用了以下设计思想:
-
按职责拆分模块,降低耦合。在当前实现中,
hanamikoji_action并没有直接把所有逻辑揉在一起,而是先调用calc_current_state还原局势,再调用build_op_scenarios构造对手可能状态,随后调用build_scenario_evals进行评估,最后再根据是否需要响应,分别走候选生成与决策流程。 -
状态集中封装。代码中定义了
GameState,把 my_cards、op_cards、known_removed、unknown_removed、need_response、used_actions等信息统一封装起来。这样规则变化时只需要扩展状态结构,而不必在多个函数之间插入新的零散变量。 -
常量参数化。代码中把
RESPONSE_ALPHA、RESPONSE_BETA、KEEP_BONUS、SCORES、TOTAL_CARDS都提取为了常量,而不是把这些值写死在具体方法中。
软件度量
→ 📖 Q3.7(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块决策能力很强?”,尝试提出一些可能的定量分析方式或测试方式。
我们使用 ai 快速实现了一个简单的随机出牌的 T3 算法,并且修改了课程组提供的test.js文件,实现了一个随机生成牌局进行对战的简单测评程序,我们通过让自己的程序和随机出牌算法进行对战的方式来度量我们程序的有效性。
我们模拟了10000场对战,以减少随机性,实验结果表明:
- 在我们实现了基于模拟对局情况的贪心算法后,这个算法对战随机算法的胜率大概是 65 : 35。
- 我们在此基础上添加了对场上未知牌的处理后,胜率来到了70 : 30。
这说明我们设计的程序已经稍微具备了一定的决策能力,并且后续的程序模块的添加也具有一定的作用,
总结
→ 📖 Q3.8(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026年4月8日 23:00
→ 📖 Q3.9(I) 请写下本部分的心得体会。
在本次实现过程中,我体会到相比直接追求“策略最优”,更重要的是先把状态恢复与流程拆分清楚。尤其是在处理历史记录、待响应动作和未知信息推断时,稍有疏忽就会导致后续决策全部出错。同时在选择决策策略时,我体会到了集思广益的重要性,如果我们两个人各自独立思考,也许做不出多么好的策略。但在我们讨论的过程中,能想到的点子越来越多,才有了我们现在复杂的决策算法。
结对项目总结
结对过程回顾和反思
→ 📖 Q4.1(P) 提供两人在讨论的结对图像资料。

→ 📖 Q4.2(P) 回顾结对的过程,反思有哪些可以提升和改进的地方。
回顾整个结对过程,有以下几点值得改进:
-
沟通节奏:在部分环节(尤其是 T2 的字符串解析设计)我们各自理解了一会儿再讨论,导致两人分别在脑子里形成了不同的方案,后来合并时花了额外时间统一。如果遇到稍复杂的逻辑就立刻一起在纸上画出来,效率会更高。
-
驾驶员/导航员分工:有时导航员的建议没有被充分采纳就直接跳过了,这违背了结对编程的初衷。应该在继续之前明确确认两人都理解并同意当前方案。
-
commit 频率:实际编码期间有几次实现了一段功能后忘记 commit,导致某次代码跑不通时回滚麻烦。应当养成"每完成一个可测试的小功能就 commit"的习惯。
→ 📖 Q4.3(I) 锐评一下你的搭档!并请至少列出三个优点和一个缺点。
优点:
- 逻辑缜密,考虑问题周到。
- 时间观念强,守时。
- 对新技术了解很全面。
缺点:
长得像我初中班主任
在选择算法时执着于性能,较为激进。
对结对编程的理解
→ 📖 Q4.4(I) 说明结对编程的优缺点、你对结对编程的理解。
我认为结对编程最大的优点,是能够在开发过程中及时发现问题、减少低级错误。两个人一起写代码时,一人负责实现,另一人负责检查和思考整体逻辑,更容易发现遗漏的边界情况、设计不合理之处。此外,两个人一起商量也更能生产出更好的想法,更优秀的算法策略。
不过,结对编程也存在一些缺点。如果两人的思路差异较大、沟通不充分,就容易出现频繁争论,反而拖慢进度。另外,结对编程对双方的投入程度要求较高,如果一方始终处于“旁观”状态,就无法真正发挥效果。
代码实现提交
→ 📖 Q4.5(P) 请提供你们完成代码实现的代码仓库链接。
7fenfen/BUAASE2026-PairPogramming
附录
附录 A:基于 PSP 2.1 修改的 PSP 表格
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | ||
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | ||
| - Technical Background | - 了解技术背景(包括学习新技术) | ||
| - Coding Standard | - 代码规范 | ||
| - Design | - 具体设计(确定怎么实现) | ||
| - Coding | - 具体编码 | ||
| - Code Review | - 代码复审 | ||
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | ||
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | ||
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | ||
| - Size Measurement | - 计算工作量 | ||
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | ||
| TOTAL | 合计 |
浙公网安备 33010602011771号