[P] 结对项目:花见小路
[P]结对项目:花见小路
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 2026年春季计算机学院软件工程 |
| 这个作业的要求在哪里 | [P] 结对项目:花见小路 |
| 我在这个课程的目标是 | 获得软件工程师应有的初步能力 |
| 这个作业在哪个具体方面帮助我实现目标 | 体验结对编程,从中收获经验与成长 |
〇、前言
→ 📖 Q0.0(P) AIGC 声明
本组提交的代码包含一定 AIGC 辅助生成的部分。使用的模型主要是 Gemini3.1 与 Claude Opus 4.6,使用范围主要包括:理解分析题目、补充测试样例、辅助完成 T2/T3 的部分底层方法实现、帮助整理
questions.md中的文字内容。最终代码和文字内容均经过人工检查和修改。
→ 📖 Q0.1(P) 开始做题时间
记录时间:2026-04-04 15:00
→ 📖 Q0.2(I) 前期调查
请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。
I. 没有听说过
I. 没有听说过;
II. 仅限于听说过相关名词;
III. 听说过,且有一定了解;
IV. 听说过,且使用 Wasm 实际进行过开发(即便是玩具项目的开发)。
请如实标注在开始项目之前对桌游花见小路的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
I. 不了解玩法和规则
I. 不了解玩法和规则:
II. 听说过,且有一定了解;
→ 📖 Q0.3(P) 结束 Guide 的时间
记录时间:2026-04-04 15:46
一、Chapter.1 七色之缨 (T1: 胜负判定)
→ 📖 Q1.1(P) T1 开始时间
记录时间:2026-04-10 10:00
→ 📖 Q1.2(P) 开发历程与耗时预估
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 5 | 5 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 15 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 10 | 13 |
| - Coding Standard | - 代码规范 | 0 | 0 |
| - Design | - 具体设计(确定怎么实现) | 5 | 2 |
| - Coding | - 具体编码 | 15 | 11 |
| - Code Review | - 代码复审 | 10 | 5 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 5 | 4 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 10 | 13 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 15 |
| - Size Measurement | - 计算工作量 | 5 | 3 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 5 | 3 |
| TOTAL | 合计 | 90 | 79 |
- 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
首先大致查阅了 AssemblyScript 官方文档,了解了 Wasm 与 JS 之间的数据传递机制。遇到测试环境配置报错(ES Modules 问题),通过修改 package.json 的 type 字段解决。最后编写了 6 个简单的本地自动化测试用例覆盖全部规则。
→ 📖 Q1.3(P) 辅助函数设计
中间量设计包括my_score,opp_score,my_tokens,opp_tokens等,整体判断逻辑已经很清楚,没有再设计辅助函数,就按照要求依次判断就搞定了。
→ 📖 Q1.4(I) 避免漏判规则(独立思考)
为了避免漏判和顺序报错,我们在写代码时拒绝复杂的 If-Else 套娃。我们采用的做法是‘先统计,后判定’:前面只管通过循环干干净净地把双方的分数和标记总数算出来单独存好;到了最后一步,再把 分数>=11 和 标记>=4 提成并列的布尔条件,单独判断双方谁拿到了‘胜利条件’。如果条件有冲突(比如双方都有一项达标),再统一交给平局胜负规则做一次兜底。
→ 📖 Q1.5(P) 测试用例设计
按照课程组要求,我们编写了 my_test.js 进行自动化断言,共覆盖了 6 类情况:
- 分值溢出胜利:一方分数达到11分。
- 标记数达标胜利:一方获得至少 4 枚标记且对手未到 11 分。
- 未决状态继续游戏:前两小轮结束时尚未满足任何结束条件。
- 第三轮分值决胜:第三轮结束,双方分数不同,高分胜。
- 第三轮最高档位决胜:第三轮结束同分,判定最高档位 G/F。
- 最终同档平局判定:第三轮结束同分,最高档位均中立,且双方在 D/E 档位僵持,引发规则穷尽返回平局(2)。
→ 📖 Q1.6(I) 对 TDD 的理解
‘先写测试再实现(TDD)’像是在进行一场纯粹的脑力训练,要求我们在敲代码前就把所有可能的边界情况和输入输出想清楚。这种方式虽然能提供极高的代码安全感,但前提是任务本身的规则范式必须非常死板和明确(如 T1 的计分规则)。
相反,‘先实现再补测试’则更契合面对复杂未知问题的思维惯性。对于像 T3 算法推演这种没有标准答案的高难度任务,先集中精力探路并把核心代码骨架搭起来,等业务跑通后再回头补齐边缘防崩溃测试,我认为显然是减少无用功、提升开发效率的更佳选择。
→ 📖 Q1.7(P) T1 结束时间
记录时间:2026-04-10 11:19
→ 📖 Q1.8(I) 心得体会
第一次做领航员()。
在此次 T1 的开发中,我的视角主要集中在全局逻辑的严密性和测试的覆盖率上。得益于结对编程的模式,当队友在快速推进 AS 代码时,我能够以‘旁观者清’的角度,专注审视胜负判定中的边缘隐患。在编码过程中,我负责设计了那几组的黑盒测试用例。感觉就是比一个人更安心一点,两个人会互相弥补对方的思维漏洞。
二、Chapter.2 不祥之影 (T2: 残局推演)
→ 📖 Q2.1(P) T2 开始时间
记录时间:2026-04-10 12:15
→ 📖 Q2.2(P) 开发历程与耗时预估
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 5 | 4 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 11 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 10 | 10 |
| - Coding Standard | - 代码规范 | 0 | 0 |
| - Design | - 具体设计(确定怎么实现) | 5 | 4 |
| - Coding | - 具体编码 | 15 | 15 |
| - Code Review | - 代码复审 | 5 | 6 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 5 | 3 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 5 | 7 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 15 |
| - Size Measurement | - 计算工作量 | 5 | 3 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 5 | 5 |
| TOTAL | 合计 | 80 | 83 |
→ 📖 Q2.3(P) T1 代码复用
代码似乎没什么复用。因为 T1 主要是作为‘结算模块’处理最终数组,而 T2 更关注于解析原始且多变的阶段性字符串(history),两者的输入粒度和核心职责由于完全不同而解耦了。尽管没有复制具体的代码片段,但我们在 T1 中打磨出的对一维数组空间扁平化压缩的思想,以及对 WebAssembly 类型机制避坑经验,被完美重用到了 T2 解析器的底层构建之中。
→ 📖 Q2.4(I) 适应需求变更的设计思想
在实现时我们着力贯彻了‘数据结构与业务逻辑解耦’的原则,避免引入‘硬编码(Hardcode)’带来的僵硬感。比如对于花见小路中人物的分数、牌数等关键规则,我们统一定义成了全局常数数组,而不是写死在判定流里,这样将来如果扩展人物或者调整输赢阈值,只需改动表头的一行配置即可;同时在判定分支结构上,我们保持函数的独立性,这种扁平的单一职责设计哪怕之后面对完全颠覆的决策算法,也能充当稳固的基础微件随插随用。
→ 📖 Q2.5(P) 头脑风暴:X 牌的处理
T2 的操作记录不带 X,消除了“扣牌”的不确定性,使对局变成了一个绝对确定的状态机。如果加上 X 失去这部分信息,我们就失去了确定性。此时必须引入概率论和记牌器模型:用全集 21 张牌 - 场上明牌 - 己方手牌推导剩余未知牌池,基于概率分布去估算对手 X 牌的组成,从而推算小轮结束后的期望状态。
→ 📖 Q2.6(P) T2 结束时间
记录时间:2026-04-10 13:38
→ 📖 Q2.7(I) 心得体会
T2开发中,我还是领(tang)航(ying)员(gou)😀。主要精力放在了数据结构和边界测试上,感觉当领航员会不由自主地对着队友的代码挑刺吧,但是某种意义上这也是结对编程效果更好的原因之一吧。
三、Chapter.3 道途之荆 (T3: AI 策略引擎)
→ 📖 Q3.1(P) T3 开始时间
记录时间:2026-04-10 15:00
→ 📖 Q3.2(P) 开发历程与耗时预估
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 10 | 8 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 20 | 15 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 20 | 17 |
| - Coding Standard | - 代码规范 | 5 | 5 |
| - Design | - 具体设计(确定怎么实现) | 30 | 25 |
| - Coding | - 具体编码 | 90 | 97 |
| - Code Review | - 代码复审 | 15 | 13 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 15 | 12 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 20 | 18 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 10 | 12 |
| - Size Measurement | - 计算工作量 | 5 | 3 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 20 | 18 |
| TOTAL | 合计 | 260 | 243 |
主要还是查阅了 README 中 T3 的规则说明和 AssemblyScript 的官方文档,确定了要用一维数组去追求极限性能。因为 T3 是一场性能对抗,前期也是看了看 src/T3/hanamikoji-engine.js,搞清楚了 history 这个长字符串的拼接格式和局势推进流转的方式。实现时,先写了一个 parse_areas 解析器把当前的明面战况还原成我们能算的数组;接着我们觉得光顺着步骤出牌太傻了,所以引入了一套基于最坏情况打分(Minimax)的推演组合方案:把对手能选走的情况全部暴力枚举出来,自己挑选分数最大化的打法,并且根据当前可用行动动态选择打出 1、2、3 还是 4。
遇到的问题:第一是死牌浪费问题。贪心算法总是用好牌去抢已经笃定能赢的角色,后来我们引入了未知牌池的“信息追踪”,一旦测算出已构成绝对压制,就不再给该角色增加打分,AI 瞬间就变聪明了;第二个问题是一个非常坑的 Wasm 运行时 Bug,当遇到对方藏牌动作带有字母 X 时,用它减去 'A' 强制寻址会导致 Int8Array(7) 越界崩溃,导致全盘报错。后来我们在代码里补上了 0~6 的安全防线才解决,配合自己在本地写的 30 组极限压力测试,逻辑才彻底稳健。
→ 📖 Q3.3(P) 理论最优策略探讨
《花见小路》是一个经典的非完美信息博弈,且状态空间有限。若有更充足算力可以考虑以下:
- CFR(反事实遗憾最小化算法)是目前解决非完美信息扑克游戏的最优解。通过引入信息集对所有可能的发牌组合做分布迭代,找到纳什均衡策略。
- MCTS (蒙特卡洛树搜索) + 信心上界 (UCT):利用模拟对局到底的方式,取代我们现在手写的固定参数启发式函数 (
evaluate_board_score)。当前我们在动作3/4只往前看了1层,但由于花见小路的局面收敛较快,若利用 MCTS 也许能看深 2~3 层。
→ 📖 Q3.4(P) 实际采用的优化策略
我们实际采用了 Minimax 搜索配合贪心评估函数。
对于行动类型的选择,一开始我们图省事定死了 1->2->3->4 的出牌顺序,但发现这极其容易被针对。后来我们改成了‘平行打分机制’:在每回合,AI 查验自己这轮还能打哪些行动,然后把当前手牌分别假装套入这些可用行动去算分,哪个行动算出来的终局预测得分高,就打哪个。比如如果起手有全场最关键的 G,它就会动态优先发动密约(动作1)把 G 藏起来保护。
就选牌最优化而言,对于最纠结的赠送(动作3,给3选1)和竞争(动作4,给4分组),我们用上了极小化极大(Minimax)博弈思想。把所有的给牌组合(比如 C(n,3) 和 C(4,2) 全排列)都用 for 循环暴力枚举出来。在推演中,假定对手极其聪明,肯定会挑走对我们伤害最大的牌。我们在所有这些‘最坏情况’里,挑出一个能让我们留存得分最高的组合给对方。
最后在实现上,核心都在重写的 evaluate_board_score 函数里。我们抛弃了单纯论卡牌分值,只看终局效果:主要考量分差拉扯、距离 11 分或 4 个标记的吃紧程度。 我们还在代码里引入了 unknown_pool(未知数计算:用总牌单减去已知明牌和自己丢掉的牌)。靠这个池子,代码就能在推断出‘即使剩下未知的牌全给我我也赢不了’时判定绝对败局,或者判定绝对胜局。一旦触发绝对锁定,代码就把投入边际得分直接砍为 0,防止把宝贵的好牌浪费在“已经死局”的列上面。同时为了保证满足 2000 毫秒的性能红线,这些逻辑全部采用 Int8Array 展平计算,没有去套任何慢的类或复杂对象。”
→ 📖 Q3.5(P) T2 代码复用
对底层的手牌解析遍历(parse_areas / 获取当前分布)进行了一定程度上的复用和强化,但将原本只有局部视野的静态 get_dynamic_value 改为了用于开局牌序快排的辅件。整体的决策内核(hanamikoji_action)由于策略维度由局部最优切换至全局优化,因此我们彻底重写并重构了以 evaluate_board_score 为中心的框架。
→ 📖 Q3.6(I) 提高既存代码适应性的冗余设计
我个人觉得想要代码能扛得住将来的需求大改,最关键的思想就是把经常变的地方和死规矩解耦开。比如,尽量把固定的业务逻辑和底层核心算法拆成独立的模块,各自管各自的事;遇到那些写死的魔法数字(比如多少分赢、几张牌),最好能一开始就抽出来当做全局参数或者配置表来读。另外在设计冗余方面,我的体会是,如果在写核心函数时哪怕稍微麻烦点,多传入几个当前还用不上的‘上下文环境参数’或者预留好报错后能兜底退出的防雷机制,真到了需求变更需要加东西时,这些不起眼的‘冗余’反而能救命,避免被迫去翻来覆去地改函数签名。
→ 📖 Q3.7(P) AI 有效性的度量
设计一些基线方法,如纯随机出牌,简单贪心作为基线。如果通过大量的随机对局,模型都有很高的胜率,那么就可以定量证明模型的决策压制力。
→ 📖 Q3.8(P) T3 结束时间
记录时间:2026-04-10 19:03
→ 📖 Q3.9(I) 心得体会
在 T3 阶段作为‘驾驶员’负责具体的代码落地,主要的挑战在于如何用 AssemblyScript 实现博弈推演算法。编写 Minimax 的组合枚举时,由于环境受限,我必须通过多层嵌套循环,并把复杂的局势降维成基础的一维数组来处理,这对编码时的逻辑严密性要求很高。在这个耗费精力的手写过程中,结对编程确实发挥了实打实的作用:当我在专注于循环和传参时,领航员在旁边帮我梳理大框架的边界。特别是在排查那个由隐藏字符 X 的 ASCII 码相减导致的底层数组越界 Bug 时,由于 Wasm 的报错极不明显,如果没有两人及时互相校验思路、同步走查代码,单人定位可能会卡住很久。这次体验让我真切觉得,在面对相对复杂的逻辑落地时,结对编程确实能实打实地提升容错率。
四、结语:落樱之庭
→ 📖 Q4.1(P) 结对照片


→ 📖 Q4.2(P) 结对反思
- 测试驱动(TDD)介入得还是稍晚了一些
反思:前期我们把大量精力花在了业务逻辑和数据结构的推演上,直到最后才补全了那 30 组自动化压力测试。这也导致了那个极其隐蔽的Int8Array越界崩溃 Bug(也就是对方历史记录自带不可见字符 X 时导致寻址溢出)被压到最后环节才暴露出来。
改进:应该在写最基础的parse_areas方法时,就随手同步写几个异常字符的输入单测,做到“写一个接口就固化一个测试”,防止底层隐患堆积到上层爆发。 - 驾驶员(Driver)与领航员(Navigator)的节奏磨合可以更好
反思:在实现 Minimax 的深层嵌套逻辑(四层for循环暴力枚举)时,由于思想比较复杂,有时驾驶员(写代码的人)会陷入专注敲代码的状态,写得太沉浸而导致领航员(审查的人)短暂跟不上思路,变成了单人作战。
改进:应该引入严格的“番茄钟机制”定期强行交换角色,并要求驾驶员必须随时嘴说出当前这一行要敲什么意图的代码,保证两人的思想波段 100% 同步。 - 对特定的技术栈特性需要更深的前置预研
反思:我们在把逻辑迁入 AssemblyScript 时遇到了一些折磨。原本以为 AS 只是带类型的 TypeScript,但实际上由于 WebAssembly 底层的静态化与扁平化设计,很多高阶容器及语法糖(比如原本顺手写的Array.map等)受限制,直接导致了一次较大面的返工重构。
改进:对于陌生的执行环境,在着手写庞大的核心逻辑前,应该先花半小时写一两个“沙盒型”的练手小脚本,彻底摸清它的语言边界和底层特征再开工。”
→ 📖 Q4.3(I) 锐评搭档
我的队友是老朋友高藩,但是这是第一次如此并肩作战。先说优点,队友编程能力夯爆了(稳稳的很安心),执行力也很强,总是能带着爱拖延的我推进度🙇,还有就是思路十分甚至十二分的清晰,真是在全过程稳了无数次方向。至于缺点嘛,容易聊点题外话,两个人还是太松弛了🤭
→ 📖 Q4.4(I) 结对编程的理解
体验一番下来,我感觉真实的结对编程并不像理论中那样轻松的‘1+1>2’,它其实是一项非常消耗精力的工作😴。最大的感受是,它逼着你必须把脑子里模糊的想法,实时且清晰地解释给队友听,这直接根除了平时单人构思时‘差不多得了’的心态。虽然长时间保持对话和高压审视容易让人疲惫,但在排查诸如内存越界这种隐蔽 Bug、或是推演树状算法时,那种有人随时帮你兜底逻辑盲区、避免独自卡壳一整天的踏实感,确实给人一种爽感。
→ 📖 Q4.5(P) 代码仓库
🔗 Github 仓库链接:https://github.com/S7AM1NA/BUAASE2026-PairProgramming

浙公网安备 33010602011771号