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

项目 内容
这个作业属于哪个课程 2026年春季软件工程
这个作业的要求在哪里 [P] 结对项目:花见小路
我在这个课程的目标是 建立系统的软件工程思维,将个人零散的代码能力转化为构建复杂、可维护软件产品的工程能力。
这个作业在哪个具体方面帮助我实现目标 通过结对协作、阶段性实现与测试、需求分析和策略迭代,把编码能力进一步扩展到需求理解、模块设计、测试验证、性能权衡和团队配合这些更完整的软件工程实践中。

结对项目:博客问题清单

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

  • 工具:Copilot、Cursor
  • 模型:GPT-5.4、Claude Opus 4.6、Gemini Pro 3.1
  • 使用范围:T2与T3的业务代码

Chapter.0 wasm从安装到入门

引入

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

4月2日 14:15

调查

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

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

I. 没有听说过;√

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

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

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

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

I. 不了解玩法和规则;√

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

总结

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

4月2日 14:22 阅读完 Chapter.0

4月2日 15:07 初步了解完 AssemblyScript

Chapter.1 七色之缨

结对过程

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

4月2日 15:45

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

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。

预计耗时 30 分钟

做了什么:

  1. 查阅guide.md,初始化项目环境
  2. 阅读指导书,确定 “先判断是否有人总分大于等于11,再判断是否有人倾心标记数量达到或超过 4 。最后若round为3,进行第三小轮结束后的最终判定。” 的判定逻辑。
  3. 阅读指导书,使用 AI 辅助生成测试用例,并人工审阅检查覆盖情况。
  4. 在生成测试用例过程中,发现没有考虑非法输入的处理。在业务函数中加入了非法输入处理的相关逻辑。

设计

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

// 卡牌分数值
const SCORE: i8[] = [2, 2, 2, 3, 3, 4, 5];
// 卡牌等级
const TIER: i8[] = [1, 1, 1, 2, 2, 3, 4];
// 我方倾心标记数
let myCount: i8 = 0;
// 对方倾心标记数
let oppCount: i8 = 0;
// 我方总分
let myScore: i8 = 0;
// 对方总分
let oppScore: i8 = 0;
// 我方最高倾心标记等级
let myHighestTier: i8 = 0;
// 对方最高倾心标记等级
let oppHighestTier: i8 = 0;

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

1.写之前使用树状结构标明每一种情况下的胜负归属;
2.为一些容易搞错的情况专门设计一些测试用例;
3.注意添加异常情况的处理机制。

测试

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

[
  {
    // 一方分值达11分立即获胜
    "board": [1, 1, 0, 1, 0, 0, 1],
    "round": 1,
    "expected": 1
  },
  {
    // 一方标记达4枚立即获胜(总分不满11分)
    "board": [1, 1, 1, 1, 0, 0, 0],
    "round": 2,
    "expected": 1
  },
  {
    // 前两小轮无人达标,继续游戏返回0
    "board": [1, -1, 0, 0, 0, 0, 0],
    "round": 1,
    "expected": 0
  },
  {
    // 第三小轮结束,总分高者获胜
    "board": [1, 0, 0, -1, -1, 0, 1],
    "round": 3,
    "expected": 1
  },
  {
    // 第三小轮结束总分相同,按最高档位判定
    "board": [0, -1, 0, -1, 0, 0, 1],
    "round": 3,
    "expected": 1
  },
  {
    // 第三小轮结束总分、最高档位均相同,判定平局
    "board": [1, -1, 0, 1, -1, 0, 0],
    "round": 3,
    "expected": 2
  },
  {
    // 边界情况:总分>=11条件的优先级高于标记数>=4
    "board": [1, 1, 1, -1, 1, -1, -1],
    "round": 2,
    "expected": -1
  },
  {
    // 边界情况:数组长度非法
    "board": [1, 0, -1],
    "round": 1,
    "expected": "Error: Invalid board length"
  },
  {
    // 边界情况:board取值非法
    "board": [1, 0, 2, -1, 0, 0, 0],
    "round": 1,
    "expected": "Error: Invalid board value"
  },
  {
    // 边界情况:round取值非法
    "board": [1, 0, -1, 0, 0, 0, 0],
    "round": 4,
    "expected": "Error: Invalid round"
  }
]

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

我认为对于这个题目里的情况,先写测试再实现是一种更高效的手段,因为在编写测试的时候可以更加深刻地理解游戏胜负的逻辑,而如果先实现再补充测试则会大概率导致漏掉一些情况,到最后再通过打补丁来修复错误。

总结

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

4月2日 16:54

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划
- Estimate - 估计这个任务需要多少时间 2 3
DEVELOPMENT 开发
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 4 10
- Technical Background - 了解技术背景(包括学习新技术) 3 10
- Coding Standard - 代码规范 1 2
- Design - 具体设计(确定怎么实现) 3 5
- Coding - 具体编码 5 6
- Code Review - 代码复审 2 2
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 3 5
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 3 8
REPORTING 报告
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 1 5
- Size Measurement - 计算工作量 1 3
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 2 10
TOTAL 合计 30 69

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

这一部分虽然整体的代码量不高,却很考验人(以及AI)的阅读理解能力,需要设计者仔细阅读理解游戏的判定细节,并基于此来设计正确的分支判断。即使是借助AI来自动生成代码,也需要人来审阅一遍具体逻辑的正确性。

Chapter.2 不祥之影

准备

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

4月2日 17:13

4月3日 10:30

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

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);

预计耗时 90 分钟

做了什么:

  1. 阅读题面和 history 的格式说明,确认T2 输入的是小轮结束后不包含 X 的完整信息记录。
  2. 初始化 t2-as ,补齐 AssemblyScript 的目录结构。
  3. 确定程序逻辑为按空格切分 history,顺序扫描每一段行动记录,再用行动次序的奇偶来判断当前这段记录属于自己还是对手。
  4. 根据不同的行动类型处理牌面变化:
    • 2 表示取舍,直接跳过;
    • 1 表示密约,直接计入出牌方区域;
    • 34 需要把给出的牌和被选择的牌拆开,再分别加入双方区域。
  5. Int8Array(7) 分别维护自己和对手的牌面数量,最后逐位比较双方同种牌的数量并更新 board
  6. 在输出时考虑到 Wasm 直接返回二维数组不够方便,采用了长度为 21 的一维 Int8Array作为返回值。
  7. 初版实现完成后进行了提交,然后精简了业务代码中的多余注释和表述作为终版。

代码可复用性与需求变更

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

  1. T1 中,我们使用 Int8Array 来存储 7 个角色的相关数据,并利用数组下标 0-6 来隐式映射角色 A-G。在 T2 中我们复用了这种思想。
  2. T1 中我们使用一个长度为 7 的线性 for 循环来同步完成多种状态的统计,减少了不必要的循环次数。在T2 中我们复用了这种思想,在一个 for 循环中完成所有手牌状态的更新。
  3. T1 中我们分别维护自己和对手的状态来辅助判断。在 T2 中我们同样根据 history 的信息去维护自己和对手的手牌状态,然后根据手牌状态进行相关逻辑判断。

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

我觉得核心在于逻辑解耦,把解析字符串、更新状态和结算胜负拆成独立的函数。这样以后规则变了,我们只需要改对应的模块,不用整个重写;以及预留出对隐藏信息或异常动作的处理位以应对后续可能的数据变更。

头脑风暴环节

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

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

​ 多出了 双方共 2 张密约牌双方共 4 张弃牌 的信息,这六张牌的信息在回合中对至少一方是不可见的。

​ 如果加上X,就需要依赖概率推断和期望模型进行估计。首先通过双方初始卡组减去场上已打出的明牌及自身手牌,确定当前牌库情况;然后结合对手可能的行为偏好进行推断(如对手的密约牌 1X 更可能是一张高价值或决定胜负的关键牌,而取舍牌 2XX 更可能是作用不大 \ 收益较低的牌),在推断的基础上分配各种事件的发生概率。最后基于分配好的发生概率,使用蒙特卡洛树搜索或计算数学期望的方法进行模拟,得出一个期望上的回合得分,从而作为下一阶段对真实状态估计的依据和参考。

总结

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

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 10 8
- Technical Background - 了解技术背景(包括学习新技术) 8 5
- Coding Standard - 代码规范 2 2
- Design - 具体设计(确定怎么实现) 10 8
- Coding - 具体编码 25 30
- Code Review - 代码复审 5 8
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 5 3
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 5 5
REPORTING 报告
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 5 4
- Size Measurement - 计算工作量 3 2
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 7 4
TOTAL 合计 90 84

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

这一部分我明显地感受到把文字规则翻译成程序状态转移其实并不简单。真正动手以后才发现 history 里每一类动作对应的牌面变化都要想得很细。好在 T1 的一些数据表示方式可以直接复用,整体实现不需要完全从零开始。

Chapter.3 道途之荆

准备

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

4月3日 11:40

4月3日 13:00

4月11日 9:00

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

  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);

预计耗时 240 分钟

做了什么:

  1. 阅读题面,理解 T3 需要模拟对战,根据对局信息输出合法行动,然后 init t3-as
  2. 最初考虑过离线预计算+在线查表的思路,希望把决策强度更多放到离线阶段完成。
  3. 完成离线计算的编码,同时使用 AI 编写了在线蒙特卡洛的版本用作 baseline。
  4. 使用 AI 补充了本地随机发牌测试,并从其他小组要来了它们编写的模块。
  5. 在测试中发现,离线预计算要么计算时间过长,字典体积过大。要么字典深度不足,命中率低。对战效果不是很理想。
  6. 最终在放弃了离线求解的思路,使用轻量的启发式算法。并通过官方测试。

头脑风暴环节

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

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

比较现实的方向有三类:

  1. CFR / CFR+ / MCCFR

    把游戏状态抽象成信息集:包括当前轮次、得分标记分布、自己手牌、双方已使用行动、当前是否在响应对手的行动、对手刚给出的牌组等信息。然后通过大量自博弈迭代,学习每个信息集上的平均策略。在线阶段只要根据当前信息集查表输出动作即可。

  2. 带信念状态的在线搜索

    由于 X 带来了隐藏信息,可以在每次决策时:

    • 先根据公开信息列出对手可能手牌集合;
    • 对每种可能状态做采样;
    • 在采样状态上进行有限深度的搜索;
    • 最终按期望收益或鲁棒收益选动作。
  3. 离线策略 + 在线修正

    更实用的折中方式,先用 CFR 类方法得到一个基础策略表,再在对局时做小规模局部搜索或重排,更灵活,不拘泥于离线策略,也能缓解离线策略命中率低的情况。

需求建模和算法设计

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

最后采用的是启发式算法

怎么选择行动类型

  1. 先根据 history 解析出当前局面,得到:
    • 我方和对手已经用过哪些行动;
    • 当前是否正处在等待我方响应的阶段;
    • 双方当前已知的场面牌、暗置牌和弃牌信息;
    • 我是本小轮先手还是后手。
  2. 再根据当前手牌和已用动作,枚举所有合法动作。
  3. 对每个合法动作计算一个分数,最后选分数最高的那个。

选牌如何更优

  1. 密约

    把某张牌暗置到自己区域后,程序会估计对手下一步的动作,并用这个结果反推这张密约是否值得。高价值牌或能稳住控制权的牌会更容易被保留下来。

  2. 取舍

    被丢掉的牌会影响方案的得分。越高价值、越可能影响胜负的牌,被丢弃时的扣分越多,从而避免程序轻易扔掉 F G 或关键的 D/E

  3. 赠予

    采用悲观评估

    • 枚举对手所有可能的选择;
    • 看哪一种选择对我最差;
    • 用这个最差结果给当前三张组合打分。
  4. 竞争

    竞争需要把四张牌分成两组交给对手选,此时程序会把各种分组方式枚举出来,再比较对手分别拿左右两组的情况,选择我方结果中更差的那一个作为打分依据。

  5. 响应

    如果当前是我方在响应对手的赠予或竞争,直接选评分最高的响应结果。

如何编程实现

  1. 状态压缩

    把牌的多重集 u16 mask 表示。因为 A-G 七类牌的数量上限固定,这样表示适合频繁做加减和比较等操作。

  2. 动作生成

    get_legal_actions 枚举当前所有可能动作并去重,例如对相同牌面但来源顺序不同的组合不重复评估,避免多余开销。

  3. 局面评估

    打分主要由三部分组成:

    • 当前这一小轮里各角色的数量差;
    • 该角色结算后倾心标记是否在我方;
    • 是否直接导致胜负。

    直接胜利则给高正分,同理直接失败则给高负分;其他情况则按分值越高的角色越重要的思路累计分数。

另外还做了一个比较实用的小优化:区分先手和后手/响应。后手更需要及时响应对手给出的组合,所以评估里会额外提高高价值角色的权重。对未知的对手手牌也会根据当前公开的信息算牌再做估计,在部分场景下会做少量采样尽量让估计不要过于死板。

代码可复用性与需求变更

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

  1. 在 T2 中我们通过顺序扫描 history 来还原双方本轮的出牌情况。在 T3 中我们复用了这种思路,并从小轮结束后的结算扩展到了当前回合的状态解析。
  2. 在 T2 中我们分别维护自己和对手的牌面状态并据此更新 board。在 T3 中我们复用了这种思路,继续分别维护双方的回合区域、弃牌信息和已用行动,作为后续决策评估的基础。
  3. 在 T2 中我们的目标是根据完整信息得出最终结果。在 T3 中我们在此基础上进行修改,加入了合法动作生成、对手出牌估计和动作评分选择,将原本的结算模块扩展成了一个实时出牌模块。

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

我认为核心是实现评估权重与核心算法的分离。把评分细节抽离出来,之后不管是想微调策略还是应对规则变化,都只需要修改独立的评价模块,而不用动整套搜索框架。

软件度量

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

正确性

  • 接口正确;
  • 动作格式正确;
  • 能在引擎规则下完成整局游戏;
  • 在单次决策 2s 限制内运行。

时限稳定性

程序决策有严格的时间限制,单次测试不超时不能完全说明问题,还应统计:

  1. 平均决策耗时;
  2. P95 / 最大决策耗时;
  3. 超时率;
  4. 因非法输出或异常导致的败局比例。

决策强度

主要看胜率,比较直接的量化方式有:

  1. 固定若干对手做双循环对战,分别统计先手和后手胜率。
  2. 在随机牌堆下重复多轮对战,统计净胜场、胜率和平局率。
  3. 设计几个关键局面做专项测试,检查程序的出牌是否符合预期。
  4. 去掉部分决策机制后再对战,观察胜率是否明显下降。

如果程序能稳定通过提交测试、不超时,在多轮随机对战中胜率明显高于随机策略,就可以认为这个决策模块是有效的。

总结

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

4月3日 15:37

4月11日 10:36

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划
- Estimate - 估计这个任务需要多少时间 15 10
DEVELOPMENT 开发
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 18 18
- Technical Background - 了解技术背景(包括学习新技术) 22 32
- Coding Standard - 代码规范 5 4
- Design - 具体设计(确定怎么实现) 28 35
- Coding - 具体编码 100 105
- Code Review - 代码复审 12 18
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 10 12
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 8 10
REPORTING 报告
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 5 8
- Size Measurement - 计算工作量 2 3
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 15 15
TOTAL 合计 240 270

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

我认为这一部分是很考验我们综合能力的。它最大的难点不在于具体算法的实现上,而是在于如何设计出行之有效的方法上。通过实际的设计、推翻并重来,我领悟了实际方法并不像理论那样顺利,有些时候使用一些理论上更有效的方案反而会降低实际的胜率。这时候抛弃掉理论的先验偏见,深入到具体的实际决策层来观察问题并根据实际问题提出针对性的解决方案,才是行之有效的思路。我也认为这一部分的思路和科研的思路有异曲同工之处。

结对项目总结

结对过程回顾和反思

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

pair

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

这次结对前两章推进比较顺,T1 和 T2 的实现、测试和记录也比较连贯;但到了 T3 时,前期在离线求解和蒙特卡洛方向上投入得偏多,后面才选择更轻量的启发式方案,浪费了不少时间。我们在复杂方案的可行性评估上还可以更早一些。

如果下次再做类似任务,可以改进以下三点:

  1. 先用小规模实验判断复杂路线是否值得继续,不把太多时间耗在高风险方案上。
  2. 尽早把测试和互测环境搭起来,让策略优劣通过对战结果暴露出来,而不是到后期再集中验证。
  3. 过程记录可以写得更及时一些,边做边记,减少最后根据提交历史填补细节的工作量。

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

优点:
1.理解能力强、逻辑能力强,能很快地从题目里抓住关键信息
2.情绪稳定,情商高,即使遇到实现上的问题也会很耐心的解决,整个合作的过程很流畅
3.思维灵活,可以快速发现现有问题并找到解决的技术方案

缺点:
有的时候还是会漏掉一些题目细节的信息,需要我来提醒

对结对编程的理解

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

优点: 结对编程最大的好处是方便实时审查。两个人一起写,很多逻辑漏洞和低级 Bug 在敲键盘的时候就被指出来了,而且在 T3 这种需要反复权衡策略的任务里,两个人进行头脑风暴能比一个人更快跳出死胡同。

缺点: 存在一定的沟通成本。如果双方对某个复杂方案产生分歧,可能会在纠结中消耗不少时间。

理解: 我认为结对编程不仅仅是两个人用一台电脑,而更像是一种双人审计机制。通过把需求理解、代码实现和即时测试同步进行,让整个开发过程变得更加透明可靠,有效避免了后期推倒重来的风险。

代码实现提交

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

https://github.com/Cl0udTide/BUAA-SE-Hanamikoji

posted @ 2026-04-11 21:36  一百万  阅读(6)  评论(0)    收藏  举报