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

结对项目:博客问题清单

请将本文件在代码仓库外复制一份,一边阅读和完成结对项目、一边填写入代码仓库外的版本,或采取简记、语音备忘等方式记载较复杂问题的要点之后再补充。请不要将本文档内的作答提交到代码仓库。

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

本组使用了codex和copilot等ai模型,在chapter2和chapter3的实现部分辅助编程并提供了可靠的思路。

Chapter.0 wasm从安装到入门

引入

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

2026.3.28 19:00

调查

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

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

II

I. 没有听说过;

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

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

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

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

I

I. 不了解玩法和规则;

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

总结

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

2026.3.28 19:28

Chapter.1 七色之缨

结对过程

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

2026.3.28 19:30

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

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

预估耗时:45 分钟。

过程记录:

  1. 先阅读 README 中 Chapter.1 的规则说明,确认输入 board 的含义、返回值编码以及第三轮的判定顺序。
  2. 讨论后决定把判定过程拆成“立即胜利判定”和“第三轮最终判定”两段,避免把所有条件揉在一个大分支里。
  3. 根据规则先整理出每个角色对应的分值数组 [2, 2, 2, 3, 3, 4, 5],后续统计总分时直接复用。
  4. 实现 totalScore(board, owner),用于统计某一方当前获得的倾心标记总分。
  5. 实现 markerCount(board, owner),用于统计某一方已获得的倾心标记数量。
  6. 实现 highestTier(board, owner),把第三轮平分时的比较规则抽象成档位:G > F > D/E > A/B/C
  7. 实现 immediateWinner(board),统一处理“达到 11 分”与“达到 4 枚标记”的立即胜利条件。
  8. 在主函数中先调用 immediateWinner,若未分胜负,再根据 round 决定返回 0 或进入第三轮最终判定。
  9. 测试阶段按题目要求列出 6 类必测情况,再额外补了一个导出别名 HanamikojiJudge 的测试,确保胶水层导出也可用。
  10. 遇到的主要问题是“4 枚倾心标记获胜”与“11 分获胜”的优先关系容易写乱。最终通过先统一计算双方总分,再按题面顺序集中写在 immediateWinner 中解决。
  11. 完成实现后运行测试,检查样例与自写用例都通过,再整理问题作答。

设计

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

我们设计了 4 个辅助部分:

  1. SCORES:保存 7 个角色对应的分值,避免在多个地方重复写分数常量。
  2. totalScore(board, owner):统计某一方当前总分,用于立即胜利判定和第三轮比总分。
  3. markerCount(board, owner):统计某一方拥有的倾心标记数量,用于“至少 4 枚标记获胜”的规则。
  4. highestTier(board, owner):把第三轮平分后的比较规则离散成 4 个档位,减少主逻辑中的重复判断。
  5. immediateWinner(board):把任意小轮结束后的立即胜利条件单独封装,保证主函数结构清晰。

这样拆分后,主函数只保留“先判立即胜利,再看轮次,最后做第三轮比较”的骨架,阅读起来更接近题目原文,也更不容易写错分支顺序。

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

我的做法是先把题目规则翻成一张很明确的判定顺序表,再按这个顺序写代码,而不是边想边写。像这道题里,“达到 11 分”“达到 4 枚标记”“第三轮比总分”“第三轮比最高档位”“平局”其实是天然分层的,只要实现顺序和文档顺序一致,就能减少错判。

另外,规则判定模块一定要配套做分类测试,尤其是边界值和互相容易混淆的情况。比如“刚好 11 分”“刚好 4 枚标记”“前两轮未结束”“第三轮总分相同但高档位不同”“第三轮完全平局”这些用例,本质上就是在验证我们是否漏掉了某个分支,或者把某个分支放错了位置。

测试

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

我们设计了 7 组测试:

  1. board=[0,0,0,1,1,0,1], round=1,结果应为 1。覆盖“一方分值达到 11 分立即获胜”。
  2. board=[1,1,1,1,0,0,0], round=2,结果应为 1。覆盖“一方获得至少 4 枚倾心标记立即获胜”。
  3. board=[1,0,0,-1,0,0,0], round=2,结果应为 0。覆盖“前两轮尚未满足胜利条件,继续下一轮”。
  4. board=[1,1,0,-1,0,-1,0], round=3,结果应为 -1。覆盖“第三轮总分不同,由总分高者获胜”。
  5. board=[1,0,0,0,0,1,-1], round=3,结果应为 1。覆盖“第三轮总分相同,由最高档位倾心标记判胜负”。
  6. board=[1,-1,0,0,0,0,0], round=3,结果应为 2。覆盖“第三轮总分与最高档位都无法区分,判为平局”。
  7. 直接调用 HanamikojiJudge(...) 的别名导出进行测试。覆盖“对外导出接口可正常使用”。

这些用例基本覆盖了题目要求的 6 类核心场景,同时也检查了我们暴露给测试脚本的函数接口是否一致。

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

我认为这两种方式各有适用场景。先写测试再实现更适合规则清晰、输入输出稳定的模块,因为它能先把“程序应该表现成什么样”固定下来,这次的胜负判定就比较适合这种思路。这样做的好处是实现时目标明确,也更容易防止遗漏规则。

先实现再补测试则更适合需求还没完全想清楚,或者需要先验证一种实现方式是否可行的情况。它推进速度通常更快,但风险是实现者容易默认自己的逻辑是对的,最后补测试时可能只会验证“自己写出来的东西”,而不是验证“题目真正要求的东西”。如果采用这种方式,就更需要在补测试时强制自己按题目逐条对照检查。

总结

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

2026.3.28 20:24

Chapter.1 的 PSP 记录如下:

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计任务时间 5 5
DEVELOPMENT 开发 35 41
- Analysis & Design Spec - 需求分析 5 6
- Technical Background - 技术背景了解 3 4
- Coding Standard - 代码规范 1 1
- Design - 具体设计 6 7
- Coding - 具体编码 10 11
- Code Review - 代码复审 3 3
- Test Design - 测试设计 4 4
- Test Implement - 测试实现 3 5
REPORTING 报告 5 8
- Quality Report - 质量报告 1 2
- Size Measurement - 计算工作量 1 1
- Postmortem & Process Improvement Plan - 事后总结 3 5
TOTAL 合计 45 54

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

这一部分给我的直接感受是:哪怕题目规模不大,只要是“规则判定”类模块,就很适合先把规则拆清楚,再写成层次分明的辅助函数。这样不仅主逻辑更好读,后面写测试时也能很自然地按模块思考。

另外,我觉得结对编程在这种题目上的优势比较明显。一个人负责把题面规则翻译成程序结构,另一个人负责挑出潜在冲突和边界情况,比单独写代码更容易发现“看起来对、其实顺序不对”的问题。对我来说,这一章更像是在练习如何把文字规则稳定地落成代码。

Chapter.2 不祥之影

准备

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

2026.4.5 9:30

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

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

预估耗时:150 分钟。

过程记录:

  1. 先阅读 README 的 Chapter.2,确认输入 history 表示一整小轮的完整公开记录,输出需要同时给出双方场面牌数和更新后的得分标记。
  2. 结合题面示例,把 4 类行动统一抽象成“是否进入区域、进入谁的区域、是否需要根据响应拆分牌组”三种处理方式。
  3. 复查 T1 中的胜负判定规则,明确本题虽然不需要直接返回胜负,但轮末更新 board 的规则与 T1 的输入语义是一致的,因此保留相同的 A-G 顺序和 -1/0/1 编码。
  4. 阅读 src/T3/hanamikoji-engine.js,确认课程组在后续任务中对 history 采用“从当前玩家视角记录、默认第一个行动属于自己”的约定,并确认 3 赠予与 4 竞争的分配方式。
  5. 设计数据结构时,把“公开区域”和“密约区域”分开累计,最后再合并成输出,避免在处理中途把“暂未翻开的牌”和“已公开得到的牌”混在一起。
  6. 实现字符串解析逻辑:先按空格拆成 8 个行动,再按 - 拆出响应选择;其中 1/2 直接处理,3/4 再根据选择结果把牌分配给提供者和选择者。
  7. 为了兼容课程组脚本,导出 calc_current_statecalcCurrentStateCalcCurrentState 三个名字,并把返回值实现成长度 21 的一维 Int8Array,方便测试脚本重组为 3x7
  8. 额外补了两个本地测试:一个覆盖“完整回放并更新标记”,另一个覆盖“同种礼物数量打平时保持原有倾心标记不变”。
  9. 遇到的主要问题是 history 的视角比较容易写反。最后通过对照 README 样例和 T3 引擎中 historyViews 的构造方式,确认奇数位行动属于自己、偶数位行动属于对手,再继续实现。
  10. 完成实现后编译 AssemblyScript 模块,运行 src/T2/test.js 和本地单元测试,确认提供样例与补充用例都通过,再回填问题作答。

代码可复用性与需求变更

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

我们主要复用了两部分思路。

第一,继续沿用了 T1 中对状态的表示方式:所有和角色相关的数据都固定按 A-G 顺序存放,得分标记仍使用 1 / 0 / -1 表示“倾向自己 / 中立 / 倾向对手”。这样 T2 在轮末更新 board 时,不需要重新设计接口,后续 T3 也能直接接上。

第二,延续了 T1 “把主流程拆成小辅助函数”的写法。在 T2 里我们把加牌、删去赠予中被选的一张、比较竞争分组是否为同一多重集、更新轮末标记等逻辑拆开写,避免把 4 类行动都堆在一个超长分支里。

相较 T1,本题的修改主要在于:从“直接根据既有 board 判定结果”,扩展成“先根据完整行动记录回放本轮区域,再推导新的 board”。

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

我觉得最有效的做法,是把“稳定不变的规则”和“这次题目额外要求的流程”分开。像 A-G 的顺序、board 的编码方式、轮末比较牌数后如何更新标记,这些都属于稳定规则;而 history 怎么表示、返回二维数组还是拍平成一维数组、是否出现 X,这些更像题目版本差异。如果一开始就把两者混在一起,后面一旦需求变化,改动面就会很大。

另外,适当保留一些“面向变化”的中间层也很重要。比如这次我们把公开区和密约区分开存,虽然最终输出只需要总牌数,但这样写以后如果需求变成“中途查询当前已公开信息”或者“重新支持带 X 的不完全信息版本”,就不需要把整个数据流推倒重来。冗余不一定是多写代码,而是给未来变化预留清晰的挂点。

头脑风暴环节

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

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

T2 的完整记录比真实对局至少多出了两类关键信息:

对手通过 1.密约2.取舍 使用的具体牌。在真实对局进行中,这两类信息对另一方本来是隐藏的。

如果重新引入 X,就会变成不完全信息问题。此时程序不能再只维护一个确定状态,而应该维护“所有仍然可能的状态集合”或某种压缩后的概率分布。例如:

  1. 根据总牌数约束、自己手牌、已经公开的赠予/竞争记录,枚举或采样所有与 X 兼容的对手隐藏用牌方案。
  2. 对每一种可能方案分别回放,得到一批候选轮末状态。
  3. 再按需求取结果:如果是保守决策,可以看最坏情况;如果是平均意义上的估计,可以计算每种状态的概率并求期望。

总结

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

2026.4.5 11:48

Chapter.2 的 PSP 记录如下:

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 10 10
- Estimate - 估计任务时间 10 10
DEVELOPMENT 开发 120 92
- Analysis & Design Spec - 需求分析 15 14
- Technical Background - 技术背景了解 10 11
- Coding Standard - 代码规范 2 2
- Design - 具体设计 18 16
- Coding - 具体编码 38 28
- Code Review - 代码复审 10 8
- Test Design - 测试设计 12 7
- Test Implement - 测试实现 15 6
REPORTING 报告 20 36
- Quality Report - 质量报告 5 10
- Size Measurement - 计算工作量 3 4
- Postmortem & Process Improvement Plan - 事后总结 12 22
TOTAL 合计 150 138

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

这一部分让我更明显地感受到,“能不能把状态表示清楚”会直接决定实现难度。题目表面上是在写字符串解析,但真正麻烦的地方其实是先想明白每一种行动到底改变了哪一层状态:是进入密约区、公开区,还是根本不进入计分。

另外,这一章也让我意识到复用不是简单复制旧代码,而是复用旧题里那些已经被验证过的建模方式。T1 里统一的角色顺序和标记编码,在 T2 里继续沿用后,很多接口衔接问题自然就消失了。对我来说,这比“从零开始重写一个看起来也能跑的新版本”更有价值。

Chapter.3 道途之荆

准备

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

2026.4.05 14:00

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

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

预估耗时:240 分钟。

过程记录:

  1. 先阅读 README 的 Chapter.3 和 src/T3/hanamikoji-engine.js,确认接口不是“计算局面”,而是要输出一个合法且尽量有竞争力的行动字符串。
  2. 继续沿用前两题的 A-G 顺序、board 编码和牌数统计方式。
  3. 重点梳理 history 的视角问题:在 T3 中,自己看到的对手密约和取舍会变成 1X2XX,而赠予和竞争是公开的;这决定了我们不能像 T2 一样恢复完整状态,只能恢复“确定可见的信息”。
  4. 阅读引擎的行动校验逻辑,确认一条 history token 对应的是“一个完整的正常行动”,选择 -X-XY 不会单独进入 history,而是附在最近一次 3/4 行动后。
  5. 设计了解析器:尝试分别按“第一个行动属于自己 / 属于对手”两种假设回放历史,通过 X 的出现位置、已消耗行动、当前手牌张数是否匹配来反推出哪一种假设成立。
  6. 在状态表示上,只恢复“自己公开区 + 自己密约区 + 对手公开区”,并把对手 1X/2XX 留作未知信息,不强行猜测具体牌型。
  7. 实现响应策略时,先处理 3赠予4竞争-选择 场景:对所有可选卡或可选牌组做静态评估,选对自己局面最有利的那个。
  8. 实现正常出牌策略时,枚举所有当前合法行动:1 的单牌、2 的两牌组合、3 的三牌组合、4 的四牌组合及 3 种分组方式。
  9. 对每个候选行动,使用一个轻量启发式函数评估“在当前已知信息下,本轮各礼物种类最终更可能倾向谁”,同时给弃牌加入损失惩罚,给赠予/竞争加入对手最优响应的最坏情况评估。
  10. 遇到的主要问题是:如果只看 history 的 token 顺序,很容易把“轮到谁行动”与“谁提供了上一组 3/4 选择”混淆。最后通过把引擎里的抽牌、行动、选择流程逐步模拟一遍,并校验当前手牌张数,解决了这一点。
  11. 完成策略实现后,补了若干本地小测试,分别验证开局能给出合法行动、在赠予/竞争响应场景下能返回正确格式的 -选择
  12. 最后编译 t3-as,运行 src/T3/test.js 做自对局测试,确认程序能稳定完成整局对战,再整理 question.md 的作答。

头脑风暴环节

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

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

如果时间和资源更充裕,我认为这个游戏里更接近“最优策略”的形式,大概率不是规则堆砌,而是一个建立在不完全信息博弈上的搜索或学习模型。

一种比较直接的方向,是把它建模成类似 Kuhn Poker / Leduc Poker 那样的有限轮次不完全信息对抗,维护“可能手牌与隐藏行动”的信念状态,再用期望收益去评估动作。因为 T3 中真正困难的不是合法出牌,而是:对手的 1X2XX 到底是什么,导致我们面对的是一组可能局面,而不是单一局面。

如果继续往“理论上更强”的方向推进,比较有可能的形式包括:

  1. 基于信息集的博弈搜索。把同一观测下无法区分的隐藏状态归并到同一个信息集,在这个层面搜索行动。
  2. CFR(Counterfactual Regret Minimization)或其变体。对于有限、可枚举的对局树,这类算法很适合逼近双人零和不完全信息博弈的均衡策略。
  3. MCTS + Determinization。先对未知信息做多次采样,把每次采样得到的完整状态当作确定局面,再做蒙特卡洛搜索,最后汇总动作价值。
  4. 自博弈强化学习。用程序不断自对弈,通过策略网络或价值网络学习“给定局面下怎样出牌更优”。

如果只从课程项目的时间预算考虑,上述方法都偏重了;但从“最优策略可能是什么样”这个问题本身来看,我认为最终会落到“信念状态 + 对抗搜索 / 学习”的框架,而不是单纯的局部启发式。

需求建模和算法设计

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

我们采用的是“合法动作全枚举 + 轻量启发式打分”的策略。

首先,在每次轮到程序决策时,先解析当前 history,恢复我们能够确定知道的局面:自己已经用过哪些行动、自己公开区和密约区分别有哪些牌、对手公开区有哪些牌、当前是不是在响应对手的 3赠予4竞争。这一步不会试图还原对手 1X/2XX 的具体内容,只保留确定信息。

然后,分两类决策:

  1. 如果当前是在响应选择:
    • 3赠予,遍历 3 张候选牌,分别计算“自己拿这张、对手拿剩下两张”后的局面得分,选择最好的一张。
    • 4竞争,遍历两组候选,分别计算“自己拿这组、对手拿另一组”后的局面得分,选择更优的一组。
  2. 如果当前是正常出牌:
    • 枚举所有当前合法动作。
    • 1:枚举每一张可密约的牌。
    • 2:枚举所有两张弃牌组合。
    • 3:枚举所有三张赠予组合,并按“对手会选对我最差的那张”的最坏情况打分。
    • 4:枚举所有四张竞争组合,以及同一四张牌对应的 3 种两两分组方式,并按“对手会拿走对我最差的一组”的最坏情况打分。

评分函数的核心思想是:在当前已知牌数下,估计每一种礼物在本轮结束时更可能倾向谁,并按其对应分值加权。与此同时,我们对弃掉高价值、急需争夺的牌加入额外惩罚,因此 2取舍 不会机械地丢掉随机两张牌,而更倾向丢掉当前紧迫性较低的牌。

编程实现上,这种方法的优点是稳定、可控、易于调参,而且因为本题每回合手牌不超过 7 张,合法候选数并不大,完全可以在 2 秒时限内完成枚举与评分。

代码可复用性与需求变更

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

我们复用了 T2 的几项基础建模:

  1. 继续使用 A-G 固定顺序作为所有数组的索引顺序。
  2. 继续使用 cardIndex、加牌统计、多重集比较等底层工具函数思路。
  3. 继续把“牌数统计”和“轮末标记倾向”看作两层独立信息,而不是混写在一个流程里。

但相较 T2,本题做了两类关键修改:

  1. T2 处理的是完整公开记录,可以恢复确定状态;T3 则必须处理 1X2XX 带来的不完全信息,因此我们把恢复目标从“完整场面”降为“当前可确定的公开信息 + 自己已知的密约信息”。
  2. T2 的终点是“还原状态”;T3 的终点则是“基于状态做决策”。因此在 T2 的解析和计数逻辑之上,我们新增了候选动作枚举、最坏情况估值、响应选择逻辑等策略层代码。

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

我觉得这里最重要的设计思想,是把系统明确拆成“状态解析层”和“策略层”。前者负责回答“现在已知什么”,后者负责回答“在这些已知信息下怎么做”。如果两层直接写死在一起,那么一旦输入格式、规则细节或评分方法变化,整个实现都会被牵连。

另外,可以适当保留一些中间表示上的冗余。例如这次我们没有只保留一个总牌数数组,而是分成“自己公开区、自己密约区、对手公开区”几部分。短期看起来字段更多,但这样后续如果要改成更精细的估值函数,或者要单独分析“哪些信息是公开可见、哪些是自己独享”,就不必把解析逻辑推翻重写。

软件度量

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

我们认为至少可以从三类指标量度策略模块的有效性。

第一类是合法性与稳定性。这是最底线的指标,包括:

  1. 是否始终返回合法格式的行动。
  2. 是否会重复使用已经用过的行动类型。
  3. 是否会在选择场景下返回错误长度的 -X / -XY
  4. 在批量自对局或对不同对手的比赛中,是否会出现超时、异常退出、非法动作判负。

第二类是对局结果指标。例如:

  1. 对固定基线策略(随机策略、贪心策略、只看分值策略)的胜率。
  2. 先手和后手两种身份下的分别胜率。
  3. 多轮双循环赛中的净胜场、平均得分、平局率。

第三类是局部决策质量指标。这类指标不直接看整局输赢,而是看程序在关键局面下是否做出了符合预期的决策。例如:

  1. 3赠予 场景中,是否优先拿对自己更关键的牌。
  2. 4竞争 场景中,是否避免把明显更优的一组白送给对手。
  3. 在自己已领先某种礼物时,是否会适度减少无意义堆叠,把资源转向更紧张的争夺点。

如果要做更系统的测试,可以准备一批“人类可判断优劣”的局面样本,让程序在这些局面下做选择,再统计它与人工分析一致的比例。这样能更具体地验证策略是否真的“会思考”,而不只是“能跑完一局”。

总结

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

2026.4.05 17:32

Chapter.3 的 PSP 记录如下:

Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 15 15
- Estimate - 估计任务时间 15 15
DEVELOPMENT 开发 185 171
- Analysis & Design Spec - 需求分析 20 22
- Technical Background - 技术背景了解 15 16
- Coding Standard - 代码规范 3 3
- Design - 具体设计 35 32
- Coding - 具体编码 60 54
- Code Review - 代码复审 12 10
- Test Design - 测试设计 20 16
- Test Implement - 测试实现 20 18
REPORTING 报告 40 46
- Quality Report - 质量报告 10 12
- Size Measurement - 计算工作量 5 6
- Postmortem & Process Improvement Plan - 事后总结 25 28
TOTAL 合计 240 232

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

这一章让我最直接的感受是:一旦从“规则实现”走到“策略实现”,问题的性质就完全变了。前两章更多是在追求正确复现,而这一章开始要接受“不可能知道全部信息”的事实,并在这种不完整信息下做一个足够稳健的选择。

另外,我也更明显地体会到启发式方法的价值。它当然不是严格最优,但在项目时间有限、状态空间又不小的情况下,一个结构清楚、可解释、能稳定输出合法动作的启发式策略,往往比追求过度复杂的搜索更现实。对我来说,这一章最大的收获就是学会在“理论最优”和“工程可落地”之间做取舍。

结对项目总结

结对过程回顾和反思

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

微信图片_20260405114016_53_219

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

回顾整个结对过程,我们认为这次合作总体上是顺畅的:前期主要通过共同阅读 README 和规则来统一建模方式,中期围绕 T1/T2/T3 的实现不断切换“驾驶员”和“导航员”角色,一人负责把讨论结果快速落成代码,另一人负责盯规则、挑边界情况和检查测试覆盖。这样的分工让我们在规则类题目上能较快发现“看起来合理、但顺序不对”或“局部合法、整体不对”的问题。

不过过程中也有可以提升的地方。第一,某些阶段我们在动手写代码前讨论得还不够细,导致后面出现返工,例如对 history 视角和回合归属的理解,应该更早画成一张简化时序图。第二,测试设计虽然最终补上了,但如果能更早把“核心场景 checklist”写出来,就能更系统地指导实现,而不是边写边补。第三,在记录问题作答时,我们有时是任务做完后再回忆补写,如果能边做边用更结构化的方式记录,会更方便后续整理博客。

如果下次继续以类似方式合作,我们会优先改进三点:先统一状态模型和输入输出约定,再编码;更早列出测试清单;把过程记录拆成更细的小结,减少事后整理成本。

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

我对搭档的评价是:属于那种在规则理解和实现落地之间切换得比较快的人,合作时很省沟通成本。

优点:

  1. 读题很仔细,能及时指出题面里容易被忽略的限制条件。
  2. 讨论时愿意把自己的想法说清楚,不会只给结论,这让我们更容易快速达成一致。
  3. 对测试比较负责,能想到一些边界情况,避免我们只验证“正常流程能跑通”。

缺点:

  1. 有时会在一个局部实现上想得过久,导致推进节奏稍微慢一点;如果能更早接受“先做一个可跑版本再迭代”,效率会更高。

对结对编程的理解

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

我理解的结对编程,并不是简单地“两个人一起写一份代码”,而是通过持续讨论,把原本会埋在个人脑子里的判断过程显性化。尤其在这种规则复杂、边界多、实现时间又受限的任务里,结对最大的价值不是“多一个人帮忙敲代码”,而是多一个实时校验模型的人。

优点是能更快发现理解偏差,一个人误读规则时,另一个人通常能及时拦住。此外,很多本来容易被忽略的边界情况,会在讨论里自然暴露出来。缺点也同样存在。最直接的问题是效率并不总是线性提升,两个人同时参与一个问题,前期沟通成本会明显上升;如果没有控制好节奏,很容易变成两个人一起卡在同一个点上。

代码实现提交

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

https://github.com/ywxy12138/Real_BUAASE2026-PairProgramming

附录

附录 A:基于 PSP 2.1 修改的 PSP 表格

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