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

项目 内容
这个作业属于哪个课程 2026年春季软件工程 - 北京航空航天大学
这个作业的要求在哪里 [P] 结对项目:花见小路
我在这个课程的目标是 了解软件工程的思想和开发流程,学习团队开发的规范,提高软件开发能力
这个作业在哪个具体方面帮助我实现目标 加深对软件工程方法的理解,训练自己进行思考表达能力。

结对项目:

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

t3在优化阶段用到了vibe coding 工具:trae gpt-5.3-codex

Chapter.0 wasm从安装到入门

引入

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

2026.4.9 15:35

调查

→ 📖 Q0.2(I) 【你可以在结对结束后另行补充。】作为本项目的调查:

请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)

I. 没有听说过;

II. 仅限于听说过相关名词;

III. 听说过,且有一定了解;

IV. 听说过,且使用 Wasm 实际进行过开发(即便是玩具项目的开发)。

I. 没有听说过;

请如实标注在开始项目之前对桌游花见小路的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)

I. 不了解玩法和规则;

II. 听说过,且有一定了解;

总结

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

Chapter.1 七色之缨

结对过程

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

2026.4.9 16:06

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

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
PSP 阶段 内容 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划
- Estimate 任务整体时间评估 5 4
DEVELOPMENT 开发
- Analysis & Design Spec 需求分析 & 生成设计规格 10 5
- Technical Background 技术背景学习 20 15
- Coding Standard 代码规范 5 5
- Design 具体设计 15 10
- Coding 编码实现 25 20
- Code Review 代码复审 5 5
- Test Design 测试设计 10 5
- Test Implement 测试实现 10 10
REPORTING 报告
- Quality Report 质量报告 10 10
- Size Measurement 工作量统计 5 5
- Postmortem & Process Improvement Plan 事后总结与改进 10 5
TOTAL 合计 130 94
  1. 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
  1. 查询资料:使用https://www.assemblyscript.org手册 阅读https://blog.csdn.net/gusushantang/article/details/151833337 了解as参数规范。了解导出函数相关规则。
  2. 涉及逻辑:严格按照文档中的 胜负判定规则 编写判断逻辑
     function judge(board: Int8Array, round: i8): i8 {
     const myScore = scoreOf(board, 1);
     const oppScore = scoreOf(board, -1);
     const myCount = countOf(board, 1);
     const oppCount = countOf(board, -1);
    
     // 任意小轮结束后立即判胜
     if (myScore >= 11) return 1;
     if (oppScore >= 11) return -1;
     if (myCount >= 4 && oppScore < 11) return 1;
     if (oppCount >= 4 && myScore < 11) return -1;
    
     // 前两轮未触发立即胜利则继续
     if (round < 3) return 0;
    
     // 第三轮
     if (myScore != oppScore) return myScore > oppScore ? 1 : -1;
     return tieBreakByTier(board); //最高分倾心标记获胜
     }
    
  3. 完成 judge 方法调用的辅助函数的编写。
  4. 设计测试用例如下:
    • 分值达到 11 获胜
    • 标记数达到 4 获胜
    • 前两轮继续(返回 0)
    • 第三轮总分高者胜
    • 第三轮同分按最高档位判胜
    • 第三轮平局(返回 2)

设计

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

设计的中间量/辅助函数:

  • SCORE_BY_ID :角色分值表(A~G 对应 2,2,2,3,3,4,5)。避免硬编码。
  • scoreOf(board, side) :统计某一方总分。将算分逻辑集中,主流程更清晰。
  • countOf(board, side) :统计某一方标记数量。将计算标记物数量逻辑集中,主流程更清晰。
  • hasAny(board, side, ...) :判断某方是否拥有某档位中的任意标记。减少重复判断代码。
  • tieBreakByTier(board) :第三轮同分时的档位比较( G > F > D/E > A/B/C )。把复杂规则独立出来,提升可读性和可维护性。

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

立即ruturn:
根据规则,立即胜利条件的优先级高于第 3 轮的最终判定,一旦满足立即胜利条件直接 return,避免漏判错判。

穷举状态:
确保所有的 if-else 分支在逻辑上是完全的,只存在 >0、<0和 ==0(进入比较)三种互斥的情况。

设计辅助函数:
将“计算当前得分”、“统计标记数量”、“获取最高档位”设计成辅助函数,避免写代码时逻辑混乱

测试

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

  • 分值达到 11 获胜
  • 标记数达到 4 获胜
  • 前两轮继续(返回 0)
  • 第三轮总分高者胜
  • 第三轮同分按最高档位判胜
  • 第三轮平局(返回 2)

→ 📖 Q1.6(I) 请说明你对“先写测试再实现”与“先实现再补测试”两种方式的理解。
先写测试再实现:
如果我们先写出测试用例,我们就明确了目标。能在动手前彻底理清边缘规则(例如第三小轮复杂的判定),并且写出的代码通常更简洁,因为只要能跑通测试即可。

先实现再补测试 :
最常见,起步快,能迅速将需求转化为代码。但在处理复杂规则时,但可能需要更全面的测试来查出代码中的bug

总结

→ 📖 Q1.7(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026.4.9 15:38
→ 📖 Q1.8(I) 请写下本部分的心得体会。

我之前从未接触过 Wasm ,但是上手挺快的。AS 虽然语法接近 TypeScript,但对类型的限制(如 i8、Int8Array)在设计之初我们就得考虑数据边界。

Chapter.2 不祥之影

准备

→ 📖 Q2.1(P) 请记录下目前的时间。
2026.4.9 17:43
→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 10 10
- Technical Background - 了解技术背景(包括学习新技术) 10 10
- Coding Standard - 代码规范 5 5
- Design - 具体设计(确定怎么实现) 15 10
- Coding - 具体编码 30 20
- Code Review - 代码复审 5 20
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 5 5
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 15 10
REPORTING 报告
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 10 10
- Size Measurement - 计算工作量 5 2
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 10 5
TOTAL 合计 125 112
  1. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
  1. 设计主函数逻辑
     export function calcCurrentState(history: string, boardInput: Int8Array): Int32Array {
     // 解析历史记录
     const tokens = tokenizeHistory(history);
     // 遍历每条历史记录
     for (let i = 0; i < tokens.length; i++) {
         const typeCode; // 操作类型
         let actor = 0; // 操作玩家
    
         // 1: 密约
         if (typeCode == '1') {
             // 根据规则在场面上添加卡牌
             actor = 1 - actor;
             continue;
         }
         // 2: 取舍(不入区域)
             if (typeCode == '2') {
             actor = 1 - actor;
             continue;
         }
         // 3: 赠送
         if (typeCode == '3') {
             // 根据规则在场面上添加卡牌
             actor = 1 - actor;
             continue;
         }
         // 4: 竞争
         // 判断对手选择了哪部分
         // 根据规则在场面上添加卡牌
         actor = 1 - actor;
     }
     // 根据场面确定最终各 geisha 偏好
     const out = new Int32Array(21);
     // 组装返回值
     return out;
     }
    
  2. 完成辅助函数的编写
  3. 遇到问题:code - 'A' 这样的写法不合法,查看 AS 变量类型相关规则。AssemblyScript 是强类型,禁止隐式转换。改为 code - 'A'.charCodeAt(0)

代码可复用性与需求变更

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

独立完成T2,并未实现复用以及修改

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

采用策略模式: 游戏中有 4 种行动(密约、取舍、赠送、竞争),将每种行动的封装成独立的类或函数。如果未来引入新角色或新行动机制,只需新增相应的策略即可。

解析解耦: 将字符串记录的解析与游戏状态的变更逻辑分开,将 history 转化为数据对象再进行分析。

头脑风暴环节

→ 📖 Q2.5(P) 头脑风暴环节:

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

  1. T2 不带 X 时,能知道对手 密约 和 取舍 用了什么牌,相当于对手明牌。
  2. 每轮结束时的状态不再是单一确定的,而是所有可能局面的集合。从确定型状态机变为了非确定型状态机。
    估计策略:
    • 每步行动后做约束更新:基于手牌数、行动次数、牌总量等信息过滤不可能分支,收缩不确定性
    • 小轮结束时估计策略:
      • 最可能状态:取后验概率最大的局面
      • Monte Carlo 采样:从可行状态采样,统计标记分布和胜率估计

总结

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

2026.4.9 19:40

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

算卡牌编号的难点在于 AssemblyScript 管得太严了。习惯了C语言直接拿 code - 'A' 就能算出结果
总之这个坑不难填,关键是得养成阅读语法规则习惯。

Chapter.3 道途之荆

准备

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

2026.4.9 23:30

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

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

头脑风暴环节

→ 📖 Q3.3(P) 头脑风暴环节:

假设提供更充裕的时间和资源,这个游戏中你能找到的最优策略有可能是什么形式的?进行调研并总结分析。你还可以在任务结束后试着实现(不计分)。

这是一个不完全信息、零和、两人博弈的卡牌小游戏 。在这个游戏里,单纯的完全信息搜索或我们的蒙特卡洛树搜索无法达到真正的最优。最优策略应趋向于寻找纳什均衡

调研如下:

  1. 反事实遗憾最小化:这是解决德州扑克(不完全信息)的标准。它在博弈中计算行动与不行动的Regret,并根据遗憾值更新策略,最终收敛到纳什均衡。

    • 思考: 这个游戏的状态数比扑克少的多,但X带来的隐藏信息(对手手牌、暗置牌、牌堆)数量依然庞大。或许可以采用蒙特卡洛 CFR来加速收敛。
  2. 深度强化学习: 我们调研到ReBeL 算法将强化学习与搜索结合,在不完全信息博弈中表现出色。

    • 思考: 引入神经网络来拟合决策函数。我们可以训练一个神经网络,输入为当前的状态及概率分布,输出为各行动的胜率预测。在对局中,则无需像现在进行大量的模拟,能缩短决策时间。
  3. 残局完美求解器: 游戏的后期(只剩1-2个行动时),隐藏信息仅限于暗置牌和手牌。此时状态复杂度急剧下降。

    • 思考: 可以预先计算所有可能的残局状态并存储。在游戏最后只剩1回合时,直接查表获取收益最大的策略。

需求建模和算法设计

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

针对该任务,我们实现了一个基于ISMCTS配合启发式剪枝的方法。具体优化策略和编程实现如下:

  1. 状态确定化与信息集处理
    • 策略: 由于无法知道对手的手牌和暗置牌,我们根据已知的公开信息,随机抽牌补全缺失状态,将“不完全信息”补全成“完全信息”。
    • 实现: determinize 函数实现。我们维护了 TOTAL 数组和 p.knownUsed。通过遍历牌堆,利用 RNG 随机生成对手的暗置牌、弃牌和当前手牌。
  2. 根节点动作剪枝
    • 策略: 3/4行动,尤其是竞争,会产生庞大的分支。为了避免分支爆炸,我们引入了启发式剪枝。
    • 实现: trimRoot 函数中,我们通过一个轻量级的评估函数 rough(a) 对所有候选动作进行快速打分。保留分数最高的 MAX_ROOT_ACTIONS(在这里我们设定为 72)个动作进入 UCB 搜索阶段。
  3. 选牌与行动启发式优化
    • 策略: 卡牌的价值并不等同,我们将 A-G 的价值定义为 [2, 2, 2, 3, 3, 4, 5]
    • 实现细节:
      • 在 Rollout阶段,rolloutAct 会根据卡牌的 SCORE 总和进行打分,并且对不同的行动类型(2弃置、3赠送、4竞争)施加了 0.350.75的惩罚系数,倾向于保留高价值卡牌用于自己得分。
      • 在小轮结算评估时,将双方在各个花魁上的势力值进行对比,并通过 Sigmoid 函数平滑化作为胜率期望反给 MCTS 的节点。
  4. 决策选择
    • 在完成720次迭代后,我们并不是选择胜率最高,而是选择访问次数最多且平均收益最大的动作。

代码可复用性与需求变更

→ 📖 Q3.5(P) 请说明针对该任务,你们对 🧑‍💻 T2 中已实现的代码进行了哪些复用和修改。
在模拟的部分用到了判断每轮结果的方法,此处复用了 T2 代码以及相关的辅助函数。

  • 复用了 history 解析思路
  • 复用了行动结算逻辑
  • 复用了“状态推进”建模方式
  • 复用了组合判断/多重集匹配思想
    → 📖 Q3.6(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
  1. 数据与逻辑分离

    • 思想: 避免在代码逻辑中出现魔法数字
    • 实现: 将卡牌的总数、价值分布以及搜索参数提取到 constants.ts 中。如果未来需求变,只需修改这些常量配置,核心算法文件不需要改动。
  2. 解析器与决策器的解耦

    • 思想: 单一职责原则
    • 实现: 将历史记录与状态推演 replay.ts 与具体的决策搜索 search.ts分离。此时 Parsed 充当通信接口。如果对局的日志格式要求发生变更,我们只需重写 replay.ts 即可。
  3. 保留函数接口

    • 思想: 评估策略需要不断迭代
    • 实现: 我们将其设计为可注入的函数接口,在不改动 MCTS 情况下,可以切换不同的评估模型。

软件度量

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

优化前:
0
优化后:

1

  1. 调参测试

    • 将迭代次数ITER分别设为100、300、720、1500,实际测试发现,随着ITER数值增加,模块胜率呈现对数增长趋势,符合预期。
    • 在相同思考时间前提下,对比开启剪枝(仅保留前几个动作)与关闭剪枝两种模式的胜率,测试结果显示,开启剪枝的版本胜率更高,说明剪枝逻辑有效。
  2. 启发式评估的消融实验

    当前代码在模拟阶段出牌、根节点剪枝过程中,融入了经验设定(惩罚权重分别为0.35、0.75,同时参考SCORE的卡牌价值分布),为验证这些经验设定的作用,做了如下消融测试:

    • 剥离所有专家知识设定,实现一个纯随机Rollout版本,让其与当前带启发式偏置的Rollout版本进行对打测试。
    • 若当前带启发式偏置的版本胜率能明显碾压纯随机版本,就能直接证明,我们结合游戏理解设计的启发式函数,确实有效提升了模块的决策质量。
  3. 时间开销测试

    • 记录模块在游戏不同阶段,运行单次迭代所需的平均CPU耗时。经过多次测试,模块可稳定在2秒时限内,完成足够深度的搜索,满足实际运行需求。
      05af6c45ca200ae953e7a7f6029d6d1f

总结

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

2026.4.10 14:11

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

以前接触的许多搜索算法(如dfs、bfs、MCTS)大多是基于完全信息,状态是确定的。但这次的 ISMCTS 让我真正体会到了學習到了如何进行不完全信息博弈 。在编码的最初阶段,我试图一个ts文件写完第三问,结果导致代码堆成屎山,所以我们必须在代码结构上做解耦。

结对项目总结

结对过程回顾和反思

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

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

首先是前期想得不够清楚就开始写代码。一开始图快,尤其在 T3 里,结果后面结构变复杂。本质问题是设计阶段没有好好设计。

其次是代码复用做得不好。T1 写过的东西,在 T2、T3 里其实并没有复用到。

第三是分工有点模糊。有时候两个人一起盯一段代码,其实效率不高,这点我们做得不够好。

最后是时间预估偏乐观。越到后面,迭代时间明显不够用,我们低估了任务的复杂度。

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

优点:
理解能力强:规则阅读很快,减少沟通成本。
代码能力强:代码就能很快写出来
沟通顺畅:讨论时仔细为我解释思路,也能接受我的不同意见,协作过程比较舒服。

缺点:
有时候会偏向先写再设计,如果前期多花一点时间设计,会更好

对结对编程的理解

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

不是两个人一起写代码,而是一个人写 + 一个人想

优点:
减少低级错误:有人实时看代码,很多bug可以当场发现
思路更清晰:讨论过程中能不断修正想法

缺点:
容易分工不清:如果没有明确分工,会变成两个人一起低效操作。

代码实现提交

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

https://github.com/LIUHA1ZHU/BUAASE2026-PairProgramming

posted @ 2026-04-11 20:04  gU_gu_Ga_GA  阅读(8)  评论(0)    收藏  举报