北航2026软件工程作业 [P] 结对项目:花见小路

项目 内容
这个作业属于哪个课程 2026年春季软件工程
这个作业的要求在哪里 [P] 结对项目:花见小路
我在这个课程的目标是 学习到软件工程中的各种最佳实践,然后实际运用到学习和未来的工作中
这个作业在哪个具体方面帮助我实现目标 学习了解结对编程相关知识,进行结对编程实践

结对项目:博客问题清单

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

本代码仓库包含 AIGC 的部分。
使用的工具: Claude。
调用的模型: GLM 5,Gemini 3.1。
使用范围:1. 自动化构建依赖环境。 2. 代码规范与优化。 3. 单元测试、benchmark的实现。

Chapter.0 wasm从安装到入门

引入

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

20260402 20:19

调查

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

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

I. 没有听说过

I. 没有听说过;zjy

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

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

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

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

I. 不了解玩法和规则

I. 不了解玩法和规则:zjy

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

总结

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

20260402 20:53

Chapter.1 七色之缨

结对过程

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

20260402 21:46

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

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

首先是要全面的了解游戏规则。我们看了规则讲解视频,并实际游玩了花间小路程序游戏,来具体熟悉游戏的规则。

其次在设计判定逻辑时,我们不光按照游戏规则进行梳理,同时还进行了正确性验证,例如:两个人是否会同时达到11分?两个人是否同时获得四个花魁的青睐?从而保证规则是严谨全面的。

在设计测试样例的时候,我们首先对可能发生的情况进行归类,然后对于每一类枚举多种具体的测试样例,从而进行设计样例。

遇到的问题是如果想在一个函数中都解决这些问题,那么这个函数会很冗长麻烦。解决办法是提前写好多个工具函数,从而进行封装和调用。

设计

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

我们为这个判定模块设计了很多辅助函数,具体如下:

countHuakuis(board: Int8Array, player: i8): i8
该函数用于计算某个 player 获得的花魁青睐个数

countScore(board: Int8Array, player: i8): i8
该函数用于计算某个 player 获得的总分数

hasHuakuiAt(board: Int8Array, player: i8, indices: i8[]): bool
该函数用于计算某个 player 是否获得了 indices 中包含的特定花魁

cmp(board: Int8Array, indices: i8[]): i8
该函数用于比较双方对 indices 中的花魁的包含情况

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

首先要认真阅读游戏规则,明确游戏胜利条件的判断逻辑。之后根据胜利规则判定的优先级进行代码编写。

按照规则,首先要判定是否有一方的分数达到 11 分,若有则该方胜利。因为总分共有 21 分,因此只会有一方达到 11 分,不存在漏判。

之后再判定是否有一方得到四个花魁的青睐,若有则胜利。因为共有七个花魁,因此也只有一方能获得四个花魁,不存在漏判。

若都无法区分胜利,则按照花魁分数从高到低依次判断双方是否持有对应等级的花魁,若一方有一方没有则分出胜负。

通过上述严谨的逻辑判断,可以避免漏判、错判和分支顺序错误等问题。

测试

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

我们设计了以下测试用例:

  1. 一方分值达到 11 分而获胜
    board: [0, 0, 0, 1, 0, 1, 1], round: 1, expected: 1, desc: "1.1 11分获胜"
    board: [0, 0, 1, 0, 0, 1, 1], round: 2, expected: 1, desc: "1.2 恰11分获胜"

  2. 一方获得至少 4 枚倾心标记而获胜
    board: [1, 1, 1, 1, 0, 0, 0], round: 1, expected: 1, desc: "2.1 4枚标记获胜"
    board: [1, 1, 1, 1, 1, 0, 0], round: 1, expected: 1, desc: "2.2 5枚标记获胜"
    board: [1, 1, 1, 1, 1, -1, -1], round: 1, expected: 1, desc: "2.3 不足11分"
    board: [1, 1, 1, 1, -1, -1, -1], round: 1, expected: -1, desc: "2.4 超过11分"

  3. 前两小轮结束时尚未满足胜利条件,应返回 0
    board: [1, 1, 0, 0, 0, 0, 0], round: 1, expected: 0, desc: "3.1 第一轮未分"
    board: [1, 1, 0, 1, 0, 0, 0], round: 2, expected: 0, desc: "3.2 第二轮未分"

  4. 第三小轮结束时,总分不同,由总分高者获胜
    board: [0, 0, 0, 0, 0, -1, 1], round: 3, expected: 1, desc: "4.1 总分高获胜"
    board: [1, 1, 1, 0, -1, 0, 0], round: 3, expected: 1, desc: "4.3 恰不满4个标记"
    board: [1, 0, -1, -1, -1, 1, 1], round: 3, expected: 1, desc: "4.3 恰不足11分"

  5. 第三小轮结束时,总分相同,由最高档位倾心标记判定胜负
    board: [0, 0, -1, 0, -1, 0, 1], round: 3, expected: 1, desc: "5.1 G档胜"
    board: [0, -1, -1, 0, 0, 1, 0], round: 3, expected: 1, desc: "5.2 F档胜"
    board: [-1, -1, -1, 1, 1, 0, 0], round: 3, expected: 1, desc: "5.3 D/E档胜"
    board: [1, -1, 0, -1, 1, 0, 0], round: 3, expected: 2, desc: "5.4 A/B/C档平"

  6. 第三小轮结束时平局,应返回 2
    board: [1, -1, 0, 1, -1, 0, 0], round: 3, expected: 2, desc: "6.1 完全平局"
    board: [0, 0, 0, 0, 0, 0, 0], round: 3, expected: 2, desc: "6.2 完全无标记"
    board: [1, -1, 0, 0, 0, 0, 0], round: 3, expected: 2, desc: "6.3 A/B/C平局"

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

对于先写测试再实现的方式,可以强迫我们先对所有结果可能出现的状况和潜在的边界情况预先进行熟悉和了解,从而帮助我们梳理实现逻辑,不容易遗漏边界情况。

对于先实现再补测试的方式,可以让我们先思考如何用代码来实现现有的需求,或者减少预先编写测试的工作量,比较符合一般编程的习惯和顺序。

我认为这两种方式都是很好的范式,都有广泛的应用场景。

总结

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

20260402 22:45

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

本部分是花间小路的第一个任务,是让我们熟悉花间小路游戏规则,熟悉编程环境和语言的开门篇。

在完成本部分的过程中,我学到了很多,也感受到了先编程、后测试的软件开发流程,总体来说是很有趣的编程体验。

Chapter.2 不祥之影

准备

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

第一次时间开始:20260404 20:17
第二次时间开始:20260407 10:33

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

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

我先完整的重新阅读了一遍游戏规则,并明确了游戏具体的出牌流程。

之后按照游戏流程进行编程设计开发。

在开发的时候,遇到了诸如字符串处理、面向对象设计等问题,最后通过设计彼此各自的解决办法来解决这些问题。

代码可复用性与需求变更

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

我们对 T1 中的 cmp, hasHuaKuiAt 两个辅助函数进行了复用,用来判断某个玩家是否获得了某个花魁的青睐。

在 T2 中新增了 Card 类、Action 类、GameSnapShot 类、calcCurrentState 函数。

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

在编码实现的时候,我们采取了面向对象的设计思想。对于某一些同类的操作,我们提取公共属性和方法,并定义了 Card 类和 Action 类。

对于添加卡牌,可以调用 Card 类的 add 方法,同时还配有查询方法。

对于输入字符串的解析,可以实例化一个 Action 类对象进行解析。

头脑风暴环节

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

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

T2 中不带 X 的操作记录相比实际对局多出了对手的选择信息。具体来说:

在赠予和竞争中,操作记录包含了对手的明确选择。实际对局中,当对手进行赠予或竞争时,我们只知道对手提供了什么牌,但不知道对手最终选择了什么,这部分信息用 X 表示。

如果加上 X,我们无法准确计算小轮结束后的状态。处理方法:需要等待对手做出选择后才能更新状态。在代码中通过 hasChoice() 方法判断是否有选择信息,如果没有选择信息则暂时不处理该操作。在 process() 函数中,对于赠予和竞争操作,只有当 action.hasChoice() 为 true 时才进行状态更新。

总结

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

第一次时间结束:20260405 0:31
第二次时间结束:20260407 11:27

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

本部分相比于T1,复杂度有了明显的提升。在T2中,我们引入了 Card、Action、GameSnapshot 等类,通过面向对象的设计思想,将游戏中的各种实体和行为封装成类,使得代码结构更加清晰,也便于复用和维护。

T2 要求根据历史操作记录计算出当前的场面状态,这需要准确追踪每一张牌的去向。我们通过 GameSnapshot 类来实现这个过程,记录每个玩家的场面卡牌数量,并最终结算出倾心标记的状态。我们设计了大量的测试用例,从简单的完整回合到极端不对称的交锋,从行动顺序倒序到复杂的分组拆分。这些测试用例帮助我们发现了很多潜在的bug,比如倾心标记的继承逻辑、卡牌计数的问题等。

T2复用了T1中的 cmp、hasHuakuiAt 辅助函数,体现了良好的代码复用思想,避免了重复开发。

总的来说,T2让我们深入理解了花见小路游戏的核心机制,也为后续T3的AI决策模块奠定了基础。

Chapter.3 道途之荆

准备

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

20260407 13:49

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

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

首先,我们仔细回顾了 T2 的代码逻辑与需求,发现要在 T3 中构建决策模块,需要先获得解析比赛状态作为地基。于是我们对原系统进行了重构,将代码抽取为 T1胜负判定相关的 judge.ts 以及 T2行动解析相关的 game.ts 组件。

接着,我们通过分析决策方式将 AI 设计为了继承制。抽取了 Strategy 基类对外提供接口,在此基础上设计并尝试实现了4种不同的策略代码:随机、贪心、基于阈值计算以及固定优先级的“大道至简”策略。

在代码实现过程中,我们也遇到了一些问题。

T2 中实现的 calcCurrentState 函数在解析状态时无法正确判断我方是先手还是后手,进而导致状态计算出现偏差。解决办法是:新增了辅佐函数 getMyPos 实现准确的身份判断,并通过 getState 打包封装以规避此类问题。

部分尝试的脑暴策略如 GeminiStrategy 以及 SimpleGreedyStrategy 预期十分科学或者灵活,但在实际模拟对战中表现效果却并不理想或容易陷入局部次优。解决办法:经过反复回放和代码对战,我们最后选定了逻辑更简单直白、以固定优先级结合分数排序来决策的 MyStrategy 作为默认选择策略。

头脑风暴环节

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

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

若有充足时间与资源,我们认为这属于典形的加持下,最优策略可能的形式如下:

花见小路涉及到未知手牌和敌方暗置的密约牌,存在很大的信息不对称性。此时可以使用反事实遗憾最小化 CFR 算法计算博弈纳什均衡,或利用不完美信息版的蒙特卡洛树搜索 MCTS。

结合自我对弈机制,通过类似 AlphaZero 的结构建立价值网络和策略网络模型,利用大量海量的自我对战局数来总结出超越人类规则理解的操作方案。

限于时间成本与计算资源,在本次结对实践中我们仍然选择以经验规则配合贪心启发式搜索来进行 AI 的主干设计。

需求建模和算法设计

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

通过多轮对决,我们采用了最稳健的 MyStrategy 作为最终的提交策略,具体设计如下:

采取固定优先级执行策略,按照 密约(1) > 赠予(3) > 竞争(4) > 取舍(2) 的绝对次序。首先藏好我们必须要的强牌,随后利用赠予和竞争试探并瓜分价值,把取舍这种废牌清理留在最后回合。

_sortHandByBaseScore 方法中,我们会对当前手牌按照基础面值从低到高进行从大到小排序:

  • 密约/1:无脑选择持有的最大价值牌。
  • 赠予/3:选出手中价值最大的3张牌供对手选择。
  • 竞争/4:采取“田忌赛马”方案搭配。我们将首尾配对(c1+c4一组,c2+c3一组),即最高+最低对抗次高+次低,防止对手在做单项选择时取得太大收益差。
  • 取舍/2:果断抛弃我们手里分数最低的两张牌。
  • 对于敌方行动的选择响应:直接计算每个备选项的含牌分值,若是我方选择,基础分*10 叠加我方已有牌数,使得优先选择容易到手高分的种类。

代码可复用性与需求变更

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

我们围绕 T3 任务对早期的代码进行了一次模块体系的拆分重构:

  • 将之前在 T1 和 T2 中堆积的核心类:CardActionGameSnapshot 以及 GameStatecalcCurrentState 函数完整地复用至 T3 中。胜负判定逻辑直接挪用给了 judge.ts 供模拟评估时复用。

  • Card 中增加了对未知暗牌 X 情况直接跳过处理的兼容代码。

  • 新增 getState 辅助流程以结合玩家方位解决当前状态的更新判定。

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

在编码实现 T3 时,主要采取了如下的设计考量:

我们提炼了一个统一基础抽象类 Strategy,它定义好了公共通用方法。具体的判定逻辑下放到各个派生类中,只需要重写实现 _makeAction_makeChoice 函数即可。同时还提供了一个 createStrategy 函数通过向它传递策略枚举参数来获取各类机器人模型。如遇全新战术仅需建立新类并在工厂分支挂载一下枚举即可,无需修改主调函数系统。无论是我们随机出来的 Random 还是智能计算出的 GeminiStrategy 方案,我们均对外保证仅暴漏单一 decide(): string 入口,完美掩盖实现细节并提升健壮度。

软件度量

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

首先,通过调用引擎函数,使自研的 MyStrategy 与我们写好的基线 RandomStrategySimpleGreedyStrategy 进行相互对局100次乃至更上万次,统计其胜率,验证能否实现超过半数的长期胜率保证。

然后利用代码内部的执行测试耗时记录,收集各种分支下的响应等待时间,确保即便是最高消耗的代码也可以将平均回执稳定把控在课程要求的 2S 甚至数十毫秒的性能红线以内。

最后可以预制特殊牌型的初始盘面,从而验证我们策划在极端情况边界情况下的适用程度。

总结

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

20260407 17:49

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

T3 模块可以说是整个结对项目中最具压轴乐趣又最具有工程技术深度的一环。最大的挑战不再是将规则写对,而是从海量非完全博弈游戏方案中甄别出能够跑出好表现的高效策略。

其中我们尝试利用过 AI 生成更为数学化阈值预判模型,但在复杂的残局回选机制下很容易出现顾头不顾尾的不理想结果。最终能让程序赢得高胜率的,反而是被我们定义为以固定顺序配合降序排序的“大道至简”模型。这件事非常有警示意义:脱离实际算力的聪明想法有时往往不如朴素、健壮的局部启发规则。

另外,结对编程带来的优势在这个章节发挥了极大的效用:一方面重构代码与封装工厂模式有效提高了代码阅读感与排错速度;另一方面,两人交叉跑局、快速回放讨论先手后手导致的状态误差错误,极大降低了陷入思维死胡同的时间成本。这确实是一次无比充实且意义非凡的结对敏捷体验。

结对项目总结

结对过程回顾和反思

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

238632e82ad852b52a099cf2c527160a

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

回顾整个结对过程,我们认为以下方面可以提升和改进:

首先是时间管理方面,我们对任务完成时间预估不足,实际耗时比预期长。下次应更详细地进行任务分解和时间估算,预留缓冲时间。

然后对于任务分工,有时分工不够明确,会出现两人同时修改同一文件的情况。这一点其实也无可厚非,倒也不用太严谨的苛责。

在文档书写方面,部分思考过程和决策过程未及时记录,应该及时记录开发过程中的关键决策和问题解决方案。

以上方面都是未来我们需要改进的地方。

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

我的搭档熊熙恺同学有点非常多:很专业、很负责、很会安排分工任务。唯一的缺点是太过完美~

对结对编程的理解

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

结对编程的优点有很多:

首先是两个人可以分担工作量,不用一个人全部承担。这样任务的完成效率和质量会更有保障。

其次是两个人线下一起工作干活,可以互相监督互相催促,这样比线上彼此各自干活要效率高,任务完成度也会更高。

缺点可能是时间不好协调,如果两个人太忙很可能凑不到很多可以一起线下编程的时间。

我对结对编程的理解是,这是一种非常好的编程方式,既可以高效地完成任务,同时也能增进两人的战略友情,为以后的工作积累经验等等。

代码实现提交

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

https://github.com/Kie-Chi/BUAASE2026-PairProgramming

posted @ 2026-04-10 18:57  lucky-sheltered-boy  阅读(31)  评论(0)    收藏  举报