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

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

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

本组提交代码包含 AI 辅助。使用工具为gimini-3,使用范围为代码审查和测试样例整理。关键规则理解、方案选择、和最终提交由两人共同确认。

Chapter.0 wasm从安装到入门

引入

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

开始时间:2026/3/28 14:50

调查

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

对 Wasm 的熟悉程度分级:
II. 仅限于听说过相关名词;
主要是在其他项目前端优化的时候听说过可以使用 wasm 进行加速

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

总结

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

结束时间:2026/3/28 15:04

Chapter.1 七色之缨

结对过程

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

开始时间:2026/3/28 15:04

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

个人软件开发流程 Personal Software Process Stages 实际耗时(分钟) 预估耗时(分钟)
计划 PLANNING 4 5
- 估计这个任务需要多少时间 - Estimate 4 5
开发 DEVELOPMENT 47 50
- 需求分析 & 生成设计规格(确定要实现什么) - Analysis & Design Spec 5 5
- 了解技术背景(包括学习新技术) - Technical Background 4 5
- 代码规范 - Coding Standard 4 5
- 具体设计(确定怎么实现) - Design 5 5
- 具体编码 - Coding 10 10
- 代码复审 - Code Review 4 5
- 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) - Test Design 4 5
- 测试实现(设计/生成具体的测试用例、编码实现测试) - Test Implement 11 10
报告 REPORTING 15 15
- 质量报告(评估设计、实现、测试的有效性) - Quality Report 5 5
- 计算工作量 - Size Measurement 5 5
- 事后总结和过程改进计划(总结过程中的问题和改进点) - Postmortem & Process Improvement Plan 5 5
合计 TOTAL 66 70
  • 预估:70分钟
  • 过程记录:
    • 阅读 T1 判定规则,先整理优先级:11分 > 4标记 > 第三轮终局规则。
    • 确认函数签名为 hanamikoji_judge(board,round),返回我方是否获胜的数字判断。
    • 设计合适的判断顺序,编写函数具体代码。
    • 按情况分类书写覆盖六类规则的测试点,以求覆盖所有可能的情况。

设计

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

  • 设计了中间量:
    • scores = [2,2,2,3,3,4,5]:按 A-G 档位定义分值映射。
    • score_myscore_opponent:双方当前倾心标记总分。
    • count_mycount_opponent:双方当前倾心标记数量。
    • score_win_myscore_win_opponent:是否达到 11 分的布尔判定。
    • count_win_mycount_win_opponent:是否达到 4 枚倾心标记的布尔判定。
    • max_mymax_opponent:第三轮总分相同时用于比较的“最高档位分值”。
  • 设计了辅助逻辑:
    • 第一层:单次遍历 board 同步累计双方分数和数量,避免重复遍历。
    • 第二层:按规则优先级顺序判定(11 分优先,其次 4 枚标记)。
    • 第三层:前两轮未触发胜利条件直接返回 0;第三轮进入终局比较。
    • 第四层:第三轮先比总分,再比最高档位,仍相同则返回平局 2

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

最重要的是要明确判定的优先级。不管在哪个阶段,先获得 11 分的人获胜。经过这个判断后,已经没有人达到 11 分,因此下一级判定“如果一方获得 4 个倾心标记,且对方没有 11 分”可以只判断有无 4 标记。此时若仍没有人获胜,并且不是第三轮,就可以直接宣判平局。

如果是第三局,则直接进行下一级的判定。先判定分数,再判定获得的标记,最后如果都无法判定,就是平局。

依照优先级的顺序组织代码,即可避免“漏判”“错判”或分支顺序错误。

测试

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

  • 分值胜利(2条):
    • board = [0,0,0,1,1,0,1], round=1,我方总分 3+3+5=11,期望返回 1
    • board = [0,0,0,-1,0,-1,-1], round=1,对手总分 3+4+5=12,期望返回 -1
  • 数量胜利(2条):
    • board = [1,1,1,1,0,0,0], round=2,我方 4 枚标记,期望返回 1
    • board = [0,0,-1,-1,0,-1,-1], round=2,对手 4 枚标记,期望返回 -1
  • 前两轮未决(2条):
    • board = [1,-1,0,0,0,0,0], round=1,未达胜利条件,期望返回 0
    • board = [1,1,-1,-1,0,0,0], round=2,未达胜利条件,期望返回 0
  • 第三轮总分决胜(2条):
    • board = [1,1,1,0,0,-1,0], round=3,我方总分高,期望返回 1
    • board = [0,0,0,1,1,-1,-1], round=3,对手总分高,期望返回 -1
  • 第三轮同分比最高档位(2条):
    • board = [1,1,1,-1,-1,0,0], round=3,同分下对手最高档位更高,期望返回 -1
    • board = [0,-1,0,0,-1,0,1], round=3,同分下我方最高档位更高,期望返回 1
  • 第三轮平局(2条):
    • board = [0,0,0,0,0,0,0], round=3,双方均无标记,期望返回 2
    • board = [1,-1,0,-1,1,0,0], round=3,同分且最高档位相同,期望返回 2

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

先写测试再实现一般用于在初期阶段各个功能以及架构以及基本不变并且十分详细的情况下,需要开发者先深入思考功能设计,然后再进行实现
先实现再补测试一般是在初期架构以及接口不太确定的情况下进行,对开发者的约束较少,比较容易进行重构和修改,但是可能导致最后测试覆盖情况不够理想。

总结

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

结束时间:2026/3/28 16:10(66分钟)

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

结对编程的初体验还是十分友好的,如果是一个人编程,有任何问题只能自己钻研或者问问 AI,结对时就大相径庭了,可以一起讨论,不仅能快速解决问题,还能得到正反馈,提高自己的代码质量和速度。
虽然本部分的代码量并不大,但却是一个需要缜密思路的环节,如果有一个逻辑出错,整个函数的分数就没有了。因此为了避免之后修 bug 的繁琐过程,在此多花一点时间打磨代码质量是正确的。

Chapter.2 不祥之影

准备

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

开始时间:2026/3/28 16:15

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

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

  • 过程记录:

    • 阅读 T2 输入输出约定,确认函数签名为 calc_current_state(history, board),返回“我方计数、对手计数、结算后 board”。
    • 将实现拆成三层:dispatch_action 负责按行动类型划分;execute_action_1~4 负责更新计数;decide_board 负责轮末结算。
    • 按“我先手、双方轮流行动”遍历 history,用 is_my_turn 在每步交换视角,保证同一套行动函数可复用。
    • 对 3/4 行动先加入出示牌,再根据记录中的选择把对应牌从出示方转移到选择方,避免分支过多。
    • 设计并通过两类测试:基础流程重建测试、平票维持旧标记测试(验证平票时继承原 board[i])。
    • 调试时检查字符串下标与字符转索引是否一致;检查不同行动是否成功区分并且跳转不同的函数;检查轮末结算是否正确判断

代码可复用性与需求变更

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

  • 复用:本任务没有直接复用 T1 的函数或代码文件,T2 为独立实现。

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

在写四种不同动作的处理逻辑时,不把所有情况都写在一个函数里,而是通过一个分发函数,根据动作类型分派到掌管不同动作的函数中,这样写代码即美观又易于维护和扩展,如果增加第五种行动,那就写一个新的、专门处理这个行动的函数出来,然后添加到分发函数中就行了。

还有就是处理 board 时,尽量不要在原来的数组上改,而是要求用户传入一个副本用于存储结构,这样可以避免对原来的数据产生污染,引发不可预见的意外。

此外我们可以发现不管是我方还是敌方,每个人的操作都是相同的,即两者的操作是对称的,我们完全可以把两者的处理逻辑合并为一个,需要用到不同的手牌或操作不同的函数是,根据当前行动方获取一个当前的视角,从这个视角进行操作,就能省略一系列冗余的代码,这跟数据库中 View 的概念类似。例如我们在 dispatch_action(action: string, cards_my: Int8Array, cards_opponent: Int8Array) 这个函数中,cards_my 就是当前视角下我的牌,这个视角可以是我方也可以是敌方,总而言之就是提供了一致的接口处理相同的逻辑。

头脑风暴环节

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

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

  • 不带 X 的 T2 记录接近“完备信息日志”:每步具体牌面和选择结果都可见,基本可以唯一重建小轮结束时双方区域计数。
  • 一旦引入 X,缺失的是“具体牌身份”的信息,只保留行动结构与部分约束,此时结果不再是单一状态,而是一个可行状态集合。
  • 可行处理方式:
    • 约束过滤:利用总牌数、已公开牌、行动格式约束,不断剔除不可能状态。
    • 多状态并行结算:对每个可行状态计算轮末 board,再汇总得到每档位的胜负概率。
    • 决策输出:在信息不完全下,可采用假设对方始终采取最优策略。

总结

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

结束时间:2026/3/28 17:50(95分钟)

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

T2 的难点在于对 history 字符串的解析。因为动作 3 和 4 涉及交互,逻辑分支会迅速增加。通过将各个行动的处理逻辑解耦,我们成功把复杂的字符串还原问题转化为了简单的数组操作,最终还原为场上真实的局面。
总之这部分不是很难,关键在于优雅地处理行动分发和视角区分。

Chapter.3 道途之荆

准备

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

开始时间:2026/3/28 17:55
续做时间:2026/3/29 14:18

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

个人软件开发流程 Personal Software Process Stages 实际耗时(分钟) 预估耗时(分钟)
计划 PLANNING 8 5
- 估计这个任务需要多少时间 - Estimate 8 5
开发 DEVELOPMENT 390 340
- 需求分析 & 生成设计规格(确定要实现什么) - Analysis & Design Spec 28 20
- 了解技术背景(包括学习新技术) - Technical Background 18 15
- 代码规范 - Coding Standard 8 5
- 具体设计(确定怎么实现) - Design 52 30
- 具体编码 - Coding 180 180
- 代码复审 - Code Review 32 30
- 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) - Test Design 42 40
- 测试实现(设计/生成具体的测试用例、编码实现测试) - Test Implement 30 20
报告 REPORTING 19 15
- 质量报告(评估设计、实现、测试的有效性) - Quality Report 7 5
- 计算工作量 - Size Measurement 6 5
- 事后总结和过程改进计划(总结过程中的问题和改进点) - Postmortem & Process Improvement Plan 6 5
合计 TOTAL 417 360
  • 预估:360 分钟。
  • 过程记录:
  • 先通读 T3 接口与引擎约束,确认输入为 history + cards + board,输出必须是合法行动字符串,并且单次决策需控制在 2s 内。
  • 先搭建状态表示 WorldState,把已知信息(我方手牌、双方已用行动、场上明牌、对方暗置/弃置数量、剩余牌池)与待确定信息(对方手牌、未使用牌、摸牌顺序)拆开管理。
  • 复用 T2 的行动解析思路,分别实现“我方历史动作更新”和“对手历史动作更新”,把历史字符串逐步还原为当前局面。
  • 实现合法动作生成:当上一手是 3/4 且未响应时只生成 - 选择动作;其余情况根据剩余手牌和未用行动枚举 1/2/3/4 的所有合法组合。
  • 引入“确定化采样 + 蒙特卡洛树搜索”:每次搜索先对未知信息随机补全,再执行选择、扩展、随机模拟、反向传播。
  • 控制搜索预算:主循环以时间截止(约 1700ms)结束,预留引擎和序列化开销,避免触发 2000ms 超时。
  • 使用课程引擎做联调与回归,重点检查三类问题:非法动作、响应格式错误、时间超限;最终 npm run submit-test 通过。

头脑风暴环节

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

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

  • 从博弈建模上看,花见小路属于双人零和、含不完全信息与序贯决策的博弈,理论上的“最优策略”应接近信息集上的纳什均衡策略,而不是单局贪心最优。
  • 可行方向1:反事实后悔最小化、增强版反事实后悔最小化、蒙特卡洛反事实后悔最小化。优点是与信息集博弈天然匹配,缺点是状态空间较大、训练与收敛成本高。
  • 可行方向2:深度反事实后悔最小化、神经拟合自我博弈。用函数逼近替代表格策略,适合更复杂状态,但工程复杂度明显提升。
  • 可行方向3:信息集蒙特卡洛树搜索。工程实现相对直接,能在有限时间内给出可用策略;当前实现采用“确定化采样 + 蒙特卡洛树搜索”属于这一路线的简化版本。
  • 如果后续继续做优化,我们会优先走“规则先验 + 信息集蒙特卡洛树搜索”的混合方案:在动作生成与奖励中加入领域启发,再通过大量自对弈调参与评估。

需求建模和算法设计

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

  • 行动类型选择:

  • 先做合法性优先:每轮 1/2/3/4 行动各用一次,先过滤已使用行动,再在剩余动作内搜索。

  • 在实现中不是手写固定优先级,而是由蒙特卡洛树搜索在当前局面下从合法动作集合中选择访问次数最高的动作。

  • 选牌策略:

    • 密约:倾向把高价值或对当前 board 影响大的牌放入可保留收益的动作中(由搜索在具体局面里决定具体牌)。

    • 取舍:更倾向丢弃在当前局势下边际收益较低、且不利于后续组合构造的牌,减少关键牌暴露风险。

    • 赠予:倾向构造“对手选哪张都不至于让我方亏太多”的三张组合,用搜索结果评估两种后继局面的期望收益。

    • 竞争:将四张牌按两组划分时,重点做“拆强组/平衡组”权衡,避免一边倒地把优势组直接送给对手。

  • 编程实现:

    • 状态建模:WorldState 统一管理局面,支持状态复制、确定化采样、摸牌、应用动作、合法性校验与合法动作枚举。
    • 搜索框架:搜索节点(MCTSNode)实现上置信界选子、扩展、回传;simulate 执行随机模拟生成奖励。
    • 未知信息处理:通过确定化采样随机补全对手手牌、暗置牌和未使用牌,把不完全信息转为可模拟状态。
    • 时限控制:hanamikoji_action 以时间预算循环搜索,返回访问次数最多子节点的动作。

代码可复用性与需求变更

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

  • 复用 T1:沿用了胜负判定逻辑(hanamikoji_judge 的同类实现),在模拟结束后用 board 结果计算回报。
  • 复用 T2:沿用了“解析行动字符串并更新双方区域牌计数、再按 A-G 比较更新 board”的结算思路。
  • 主要修改:T3 新增了不完全信息处理(未知牌确定化采样)、合法动作枚举、蒙特卡洛树搜索决策流程;不再是单次重建状态,而是“重建 + 预测 + 搜索”。

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

由于我们采用的是“重建 + 预测 + 搜索”模式,且每次重建时的局面基本上都各不相同,当前的决策也依赖场上的局面,因此单独将场上的局面封装起来是十分有必要的。这样我们在进行模拟时不直接依赖输入的 history 等低层次信息,进行模拟时也更加快速。

同时可以构建一个游戏进行的 Action 封装类,用于从当前局势导入或导出合法行动,从而使存储和动作分离,更加灵活、更加可扩展。以及一个用于游戏模拟的 Simulator 类进行游戏模拟,但由于时间问题,我们暂时未能实现。

在蒙特卡洛进行采样(包括手牌和行动)时,我们使用的是内置的随机策略,此外我们可以封装一个 SampleDistribution 类根据策略对对手的行动和手牌进行采样,这样进行策略选择时更加灵活。

总之就是如下几个原则:尽量解耦合、封装、能让用户选择的地方不要写死、并且一个模块只干一件事,并且要把它干好。

软件度量

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

  • 正确性指标:在课程引擎中能完整完成对局,非法动作率为 0,响应格式错误率为 0。
  • 时限指标:统计单局内每位玩家累计决策耗时与单次决策最大耗时,确保不触发 2s 超时。
  • 强度指标:固定随机种子下,与基线策略(随机/规则)进行多局对战,统计先手与后手分开的胜率。
  • 稳定性指标:在不同随机种子下重复对战,比较胜率方差与超时/异常比例。
  • 回归指标:每次改动后重跑 npm run submit-test 与自定义对战脚本,防止策略改动引入行为回退。

对战随机策略:

1

对战简单贪心:

总结

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

结束时间(第一天):2026/3/28 20:30

结束时间(第二天):2026/3/29 18:40(分段累计 417 分钟)

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

本部分是最有挑战性的,我们将前几天了解到的蒙特卡洛树搜索算法的变种版本实装到了这个游戏上,其具体算法如何、如何进行模拟、如何找出所有合法策略、如何随机一个策略等等都是实现过程中需要解决的问题。
此外还遇到了类似 Index Out Of Range 的 bug 吧,可能确实在敏捷开发中实现一个还不太熟的算法确实有些难度,但无论如何我们最终做出了一个可以正常运行,并且对随机和贪心算法都很有竞争力的算法,总而言之是一次十分具有实践意义的经历。

结对项目总结

结对过程回顾和反思

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

附上双人讨论照片:

3

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

  • 可改进点1:更严格执行“每30~45分钟小结一次”,并在每次小结后立即提交一次小粒度 commit。这样可以减少后期集中回忆和补文档的压力,也能在出现问题时更快定位到具体改动区间。
  • 可改进点2:在现有规则分类测试之外,补充小批量随机化测试与回归测试清单。具体做法是固定随机种子生成若干边界场景,并保留失败样例库,后续每次修改后自动重放,提升测试完备性和稳定性。
  • 可改进点3:将策略相关参数(如权重、优先级、阈值)配置化,并把测试样例情况区分进一步解耦。这样后续替换策略时只改配置或单模块,不影响主流程逻辑,维护成本更低。
  • 可改进点4:为 T3 增加系统化对战评测脚本(固定种子、先后手分开统计、对手池分层),并定期导出胜率、超时率和非法动作率。这样可以减少“凭体感调参”,让策略迭代有可量化依据,也更容易发现某些对局条件下的性能退化。

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

优点 :

  • 行动迅速,能够快速在理解任务需求,在最短时间内完成任务,提高了协作效率。
  • 熟练操作各种编程或者项目管理工具,专业技能扎实
  • 思维敏捷,心思细腻,能迅速发现潜在的漏洞

缺点 :

  • 不太善于交流,沟通风格较精简

对结对编程的理解

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

优点 :
结对编程本质上是一种实时的代码审查和思路提供方法,通过队友的实时反馈,提高最终代码质量。此外如果两人在技术栈上各有侧重,可以让擅长某一部分的人来写那个模块,另一个人只用负责审查逻辑,省去了大量的查阅资料时间,提高了开发效率。此外双人协作能显著提高自己的正反馈,不被频繁出现的 bug 打击,保持项目的推进节奏。

缺点 :
结对要求双方时刻保持步调一致,这比单人开发更易疲劳,对沟通效率和默契度有很高要求。而且结对编程需要同步对方的空闲时间,如果对方一直没来或没有时间就无法开展结对编程,这是最大的一个问题,比如我们计划在 14 点开始项目,但由于各种琐碎的事,直到接近 15 点才正式开始。

我理解的结对编程不仅仅是两个人写一份代码,它更像是一种实时纠错机制。在像花见小路 AI 这种涉及大量启发式策略和不完全信息博弈的项目中,结对编程能确保每一行进入仓库的代码都经过了两个大脑的过滤,从而在根本上提升了软件的可靠性。

代码实现提交

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

仓库链接:wrongization/BUAASE2026-PairProgramming: 北航软工2026结对编程

posted @ 2026-04-07 23:38  Marigoldarly  阅读(28)  评论(0)    收藏  举报