[P] 结对项目:花见小路
| 项目 | 内容 |
|---|---|
| 这个作业属于哪个课程 | 课程链接 |
| 这个作业的要求在哪里 | 作业要求 |
| 我在这个课程的目标是 | 提升代码水平,学会团队协作,通过敏捷开发开发出预期内的应用程序 |
| 这个作业在哪个具体方面帮助我实现目标 | 体验结对编程,培养后续团队协作能力 |
结对项目:博客问题清单
请将本文件在代码仓库外复制一份,一边阅读和完成结对项目、一边填写入代码仓库外的版本,或采取简记、语音备忘等方式记载较复杂问题的要点之后再补充。请不要将本文档内的作答提交到代码仓库。
→ 📖 Q0.0(P) 【你可以在结对结束后补充】如果你的代码仓库包含 AIGC 的部分,列举使用的工具、模型和使用范围。若未使用则填写:本组提交的全部代码不包含AI补全或生成的部分。
我们的代码仓库并未包含AIGC的部分。但在结对过程中,我们确实使用了claude来辅助我们理解题目要求、探讨策略设计,在其辅助下,加上我们自己的讨论,我们在T3的策略得到两次改进。
Chapter.0 wasm从安装到入门
引入
→ 📖 Q0.1(P) 请记录下目前的时间。
2026-04-03 17:48
调查
→ 📖 Q0.2(I) 【你可以在结对结束后另行补充。】作为本项目的调查:
请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
仅限于听说过相关名词,但并未了解过
请如实标注在开始项目之前对桌游花见小路的熟悉程度分级,可以的话请细化具体的情况。(分别回答两人各自的情况)
完全不了解玩法和规则
总结
→ 📖 Q0.3(P) 请记录下目前的时间。
2026-04-03 18:11
Chapter.1 七色之缨
结对过程
→ 📖 Q1.1(P) 请记录下目前的时间。
2026-04-03 18:55
→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(例如查阅了哪些资料、如何设计判定逻辑、如何设计测试样例、遇到了什么问题、如何解决)。
在chapter1里面,我们主要查阅的资料为assemblyscript的一些语法。
判定逻辑上,由于该部分只涉及到根据最后的局面来判断获胜情况,规则十分清晰,不存在歧义性,因此我们严格按照判胜规则实现判定逻辑,将每一条规则都覆盖且正确实现,同时以规则的优先顺序来决定分支的顺序。
测试样例上,我们考虑了目标函数所有可能的返回值,以及每个返回值对应的不同局面,例如,返回值为1时,需要考虑我方对局结束时拿到11分、我方对局结束时获得四枚倾心标记且对方未获得11分、第三轮时双方都没到达11分且都未获得4枚倾心标记但我方得分较高以及第三轮时双方都没到达11分且都未获得4枚倾心标记且双方得分相同但我方拥有更高档位的倾心标记,这样保证全面充分的测试。
本部分我们未遇到明显的问题,过程较为顺利。
设计
→ 📖 Q1.3(P) 请说明你们为这个判定模块设计了哪些中间量或辅助函数;如果没有额外设计,也请说明为什么认为直接实现已经足够清晰。
我们设计了getAllScore函数来计算当前局面下某一方获得的总分数,设计了getAllSig函数来获取当前局面下某一方的倾心标记数,设计了getMax函数来得到当前局面下某一方最高分标记对应的得分。通过这三个函数,我们可以根据获胜规则的优先级在主逻辑中快速实现获胜判定的分支。
→ 📖 Q1.4(I) 请说明在这样一个规则判定类模块中,如何避免“漏判”“错判”或分支顺序错误等问题。
我认为要避免此类问题,需要一一对照着获胜判定规则来编写分支等。首先是需要把每一条规则全部都覆盖到,其次是要正确处理好每一条规则,最后是按照规则的优先顺序来编写分支顺序。规则全覆盖较为容易,每一条规则对应一个分支即可。正确处理每一条规则,在这里也比较容易,因为花见小路的获胜规则是十分清晰的,无论是11分还是4枚倾心标记(对方没有11分),规则都写得十分清楚,并没有什么歧义。最后是按规则的优先顺序来决定分支的顺序,其实题面已经将获胜规则按优先顺序排列了,排在前面的更优先,比如获得11分是一定获胜的,无论对方是怎样的状态,但如果遇到了并未事先排好优先级的规则,那么就需要仔细分析一下规则之间的关系,理解每条关系的真正意思,一个没有歧义的规则,一定能够给每条规则分配不同的优先度,这样确定优先度之后,再按优先度排序来编写分支顺序。
测试
→ 📖 Q1.5(P) 请说明你们设计了哪些测试用例,这些测试分别覆盖了哪一类规则或边界情况。
我们这次一共设计了15个测试样例,覆盖了判定函数的全部核心分支。
首先是达到 11 分立即获胜,我们分别测了我方和对手两种情况。比如 board: [1, 1, 1, 0, 0, 0, 1]、round: 1,我方拿到 A、B、C、G,正好11分,应立即返回我方获胜;对应地 board: [-1, -1, 0, -1, 0, -1, 0]、round: 2会返回对手获胜。
其次是至少 4 枚倾心标记获胜(且对手未达 11 分),也做了双方覆盖。例如 board: [1, 1, 1, 1, 0, 0, 0]、round: 1,虽然总分只有9分,但我方已拿到4枚标记,应判胜;board: [-1, -1, -1, -1, 0, 0, 0]、round: 2 则验证对手同理获胜。
对于前两轮继续游戏的情况,我们用 board: [1, -1, 0, 0, 0, 0, 0]、round: 1 和 board: [1, -1, 1, -1, 0, 0, 0]、round:2确认在未满足胜利条件时返回 0。
第三轮我们分成三类:
第一类是总分不同直接决胜,例如 board: [1, 1, 0, 0, 0, 1, 0]、round: 3(我方总分更高)和 board: [1, 0, 0, 0, 0, -1, 0]、round:3(对手总分更高)。
第二类是总分相同但按最高档位判胜,例如 board: [-1, 0, 0, -1, 0, 0, 1]、round: 3,双方同分但我方拥有 G,应判我方获胜;board: [1, 0, 0, 1, 0, 0, -1]、round: 3 则是反向情况。
第三类是第三轮平局,例如 board: [1, -1, 0, -1, 1, 0, 0] 和 board: [1, -1, 0, 1, -1, 0, 0](均为 round:3),都应为无法区分胜者返回 2。
→ 📖 Q1.6(I) 请说明你对“先写测试再实现”与“先实现再补测试”两种方式的理解。
“先写测试再实现”是在编写功能代码之前先编写测试代码,这是十分有好处的,一方面,在才刚拿到一个规格说明书或者项目需求的时候,我们对具体的功能细节是还不够清晰的,在这个时候,编写测试代码,能够让我们更好地理解需求,更深入地了解规格的细节,让我们更加熟悉;另一方面,由于编写了测试代码,因此我们对一些边界情况有了认知,在编写功能代码的时候,能够时刻提醒自己考虑到这些边界情况,会减少我们出现漏判误判的情况。
而“先实现再补测试”则是先编写功能代码,等功能代码完成之后再编写测试代码,个人认为,这种方式的好处是我们可以先不考虑测试代码的编写,以及各种复杂的测试案例的编造,一上来读完说明和需求之后就进行设计与实现,可以更加专注,同时也能更快完成代码实现。但这种方式的缺点也很明显,我认为大部分人都遇到过,那就是写完之后发现这里也是问题,那里也是问题,又需要回过头来修改代码,这样就会增加开发的时间和成本。
一个成熟的软工团队,我个人认为应该是先写测试再实现或者说测试与实现并发进行的,开发者要对需求有深入地理解,时刻将特殊情况考虑在内,才能保证代码的质量和健壮性。我认为目前我们同学可能大多都是先实现再测试甚至不测试,这在未来真正到了对正确性、健壮性要求较高的项目中是很难适应的,所以我们应该意识到测试的重要性,尽量养成先测试再实现或者两者同时进行的习惯。
总结
→ 📖 Q1.7(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026-04-03 19:47
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 4 | 3 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 6 | 5 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 6 | 6 |
| - Coding Standard | - 代码规范 | 2 | 1 |
| - Design | - 具体设计(确定怎么实现) | 7 | 7 |
| - Coding | - 具体编码 | 10 | 9 |
| - Code Review | - 代码复审 | 3 | 3 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 6 | 7 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 5 | 6 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 2 | 2 |
| - Size Measurement | - 计算工作量 | 1 | 1 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 2 | 2 |
| TOTAL | 合计 | 54 | 52 |
→ 📖 Q1.8(I) 请写下本部分的心得体会。
在本部分的结对过程中,我认为我和搭档的沟通和合作体验很好,我们积极沟通,最终较好较快地完成了该部分内容。我在之前没怎么接触过wasm,在这个过程中也对wasm了解更加深入了,而由于该部分功能实现相对简单,因此也并没有遇到什么阻碍。尽管如此,我和搭档在实现过程中也互相提醒对边界情况以及分支顺序等的考虑,并一同设计测试案例,整个过程是给我带来很多收获的。
Chapter.2 不祥之影
准备
→ 📖 Q2.1(P) 请记录下目前的时间。
2026-04-03 22:09
→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
本部分也未涉及复杂的策略等内容,规则也给的十分清楚,因此本部分我们没有查阅额外的资料。
在具体开发过程中,我们先阅读了有关历史记录格式的说明,确认每一种行动会造成何种影响,之后再开始代码编写。实现过程中,我们也单独设计一个函数来实现一个行动对当前场面的影响,这样在主逻辑当中,通过遍历调用这个函数即可得到最终的局面,并判断倾心标记的变化,
过程中需要注意的地方有两个。第一个第三类行动和第四类行动涉及到对两方的同时影响,因此要注意更新完全。第二个要清楚倾心标记移动的规则,本部分会涉及到倾心标记的移动,要准确实现移动逻辑。
整体上,本章的实现过程比较顺利,时间主要花费在理解历史记录语义和核对状态转移是否正确上。
代码可复用性与需求变更
→ 📖 Q2.3(P) 请说明针对该任务,你们对 🧑💻 T1 中已实现的代码进行了哪些复用和修改。
在本任务中,在代码上我们其实并没有对 T1 进行复用,因为在T1中,更关注的是在一个给定的最终局面下,去判断获胜情况,所以在实现的时候,我们会设计一些统计函数,来辅助判胜逻辑。然而T2中,重点变为了根据过往的历史操作,来还原最终局面,因此之前设计的种种对于最终局面统计的函数难以再使用。
但我们仍然采用了相同的设计思路,将逻辑解耦,不揉杂在一块,分离了handle函数做专门的行动处理,并设计数组来统计局面的变化。
→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
在该部分编码实现中,我认为可以通过以下设计思想与设计冗余来应对需求变更:
- 将大逻辑细化解耦,例如字符串解析、状态更新等,这样可以使主逻辑更为清晰与专注,在需求变更时,可以修改小模块而无需改动整体结构。例如,若指令格式改变,只需修改相应的解析函数。
- 在处理卡牌、标记等操作时,可以引入卡牌分数数组等辅助结构,如果未来增加具有新特性的卡牌时,可以对这些结构进行扩展减少对核心逻辑的修改。
- 预留一些接口和抽象层,例如在状态更新函数中预留一些钩子或接口,以便未来增加新的状态属性或操作类型时,可以通过实现这些接口来适配,而不需要修改现有的状态更新逻辑。
头脑风暴环节
→ 📖 Q2.5(P) 头脑风暴环节:
我们终于快要开始让程序玩游戏了!请尝试分析:T2 中不带 X 的操作记录比起实际对局多出了多少信息?如果加上 X,也就是失去了这部分信息的话,如何处理对小轮结束后状态的估计?
T2中当前使用的不带X的历史记录,相比真实对局多给了程序一个“上帝视角”信息,主要涉及的是一类和二类行动,真实游戏里对手是不知道你暗置或弃掉的具体牌的,但在不带 X 的记录中,这些牌的真实字母都直接暴露出来了,而第三类和第四类行动本身是双方玩家都知道具体情况的,因此能够完整得到最终的局面。
如果把这些本不应公开的内容用 X 遮蔽掉,其实我方丢掉的信息就是对方密约藏起来的牌和对方取舍舍弃的两张牌,同时,加上开局舍弃的一张牌,我方所不知道的信息只限于四张牌。那么最为自然的做法是对这四张牌进行枚举,枚举出不同的具体情况,并按有上帝视角时相同的做法来得到对应情况下最终的局面。由于枚举范围只在四张牌之中,因此所有的情况也并不多,能够很快得到不同情况下的局面。
总结
→ 📖 Q2.6(P) 请记录下目前的时间,并根据实际情况填写 附录 A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026-04-03 23:28
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 5 | 4 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 12 | 14 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 8 | 6 |
| - Coding Standard | - 代码规范 | 2 | 2 |
| - Design | - 具体设计(确定怎么实现) | 10 | 11 |
| - Coding | - 具体编码 | 20 | 18 |
| - Code Review | - 代码复审 | 5 | 5 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 8 | 7 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 8 | 7 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 3 | 2 |
| - Size Measurement | - 计算工作量 | 1 | 1 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 3 | 2 |
| TOTAL | 合计 | 85 | 79 |
→ 📖 Q2.7(I) 请写下本部分的心得体会。
在 Chapter.2 的实现中,我们将单纯的规则判定推进到了“状态模拟与模拟推演”层面。这部分实现下来个人觉得也是在考基本功,整体实现也不算难。这个过程中,我们对整个游戏的理解更加深入。同时,这一阶段结对的好处再次凸显:由于逻辑更细化,当我们在处理一些细节时,搭档可以随时检验与提醒,因此最终不会出现错误。
Chapter.3 道途之荆
准备
→ 📖 Q3.1(P) 请记录下目前的时间。
2026-04-04 11:02
→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
在本部分中,由于涉及到策略的选择,因此我们上网查阅了花见小路桌游是否有现成的一些优秀策略,但查阅的结果中,我们发现这些策略都是较经验化的,并不一定真的可以有较好的结果,而且这些策略形容都较为抽象,难以凝练出一套具体的策略规则。我们在经过讨论之后,为了兼顾设计时间以及函数运行时间的限制,最终采取了设计奖励函数与模拟的思路,通过模拟当前情况所有可能的行动,并计算对应行动得到的奖励,最终选择最大奖励对应的行动。代码实现过程中,由于该部分逻辑更加复杂,我们设计了很多功能函数,例如isMeStart、parseHistory等,增加代码可读性的同时也能让代码的逻辑性更强。整个实现当中,我们遇到最突出的问题即为奖励函数如何设计,以及应该把哪些因素考虑进来计算奖励。我们从最简单的分值奖励开始,再到后来将倾心标记归属、每类牌的数量以及对手的手牌情况逐渐考虑进来,进行了几次修改,完成了最终的实现。
头脑风暴环节
→ 📖 Q3.3(P) 头脑风暴环节:
假设提供更充裕的时间和资源,这个游戏中你能找到的最优策略有可能是什么形式的?进行调研并总结分析。你还可以在任务结束后试着实现(不计分)。
我们调研后认为,这类策略博弈的“最优策略”是不完全信息博弈中的信息集策略。可行形式如下:
- 博弈论求解
- 将花见小路建模为扩展式不完全信息零和博弈;
- 用CFR、CFR+、MCCFR等方式在信息集上迭代,逼近纳什均衡策略;
- 该种方式下,产出物通常是“在某个信息集下各行动的混合概率分布”。
- 搜索+估值
- 在每步做有限深度搜索(如对手响应枚举);
- 隐藏信息用采样或信念状态近似;
- 叶子节点用启发式价值函数估值。
- 自博弈学习
- 通过self-play生成对局数据;
- 用策略网络和价值网络近似决策;
- 可结合NFSP、PPO、MCTS等方法迭代提升。
综合“理论最优性”和“课程任务可实现性”,我们认为在资源充足的情况下,优先使用CFR系列,而若要在有限时间内得到高胜率,则可以考虑“启发式 + 对手最优响应建模 + 轻量搜索”的混合策略。
需求建模和算法设计
→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么选择行动类型?选牌如何更优?如何编程实现。
在策略层面,我们先定义统一的价值函数,再用它驱动行动选择。我们定义的价值函数综合三类信息:
- 基础牌值(艺伎分值);
- 当前争夺态势(该艺伎目前由谁领先、双方计数差);
- 终局优先级(11 分目标、4 标记目标、第三轮 tie-break 的高档艺伎优先)。
在这个统一价值标准下,我们的决策分三步展开
-
行动类型选择
- 每回合先解析历史,得到本轮已使用哪些行动、当前是否是响应阶段、以及手牌可组成哪些合法动作;
- 若当前是我方响应阶段,则在合法响应中选最优;
- 若是主动出牌阶段,则对所有合法动作计算分数,取分数最高者;
- 各类型动作评分思想:
- 1:该类动作偏向保留高价值牌给己方,评分为“该牌价值+固定奖励”;
- 2:该类动作用于处理低收益或风险牌,评分与牌价值负相关(价值越低越适合丢弃);
- 3:按maximin评估,对手会选对他最有利那张,取我方最坏结果作为该动作得分;
- 4:同样按maximin,按对手最佳选择评估最坏结果,并惩罚两组价值差过大的分组(避免送分行为)。
-
牌型组合选择
- 1:优先暗置“高价值且易被争夺”的牌,提升隐蔽得分效率;
- 2:优先弃掉当前局面对胜负贡献低、且不影响关键争夺线的牌;
- 3:构造“对手拿哪张都不赚太多”的三张组合,核心是压低最坏分支损失;
- 4:构造价值接近的两组,减少被对手单点利用的空间;
- 对对手3、4响应:不只看“我拿到多少”,同时还要关注对手的收益,查看看净收益
myGain - oppGain,确保拿牌后总体局面更优。
-
编程实现
- 入口与分流:主函数
HanamikojiAction(history, cards, board)先调用parseHistory还原状态,再用isChoiceTurn判断当前是“响应决策”还是“主动出牌决策”。 - 状态重建(
parseHistory):- 按一个一个token解析历史字符串,识别行动类型、出牌内容、是否已响应;
- 更新
usedActions[1..4](记录本轮是否已使用某行动); - 更新
selfPublic / oppPublic / selfSecret(我方公开、对方公开、我方暗置); - 若对手刚执行动作3或4且未响应,则写入
pendingType / pendingCards,供本回合直接使用。
- 合法候选生成:
- 响应阶段调用
findChoices:pendingType=3时生成-A/-B/...形式的可选单牌;pendingType=4时生成可选二张组(若两组同构则只保留一个);
- 主动阶段调用
findActions:- 基于当前手牌计数(
countCards)枚举所有可组成动作; - 通过
containsCards过滤非法组合; - 对动作4候选用
canonicalType4Action规范化去重,避免同一分组重复评估。
- 基于当前手牌计数(
- 响应阶段调用
- 评分与选择:
- 响应候选用
evaluateChoice评分,核心是myGainValue - oppGainValue; - 主动候选用
evaluateAction评分:- 行动1和行动2按价值函数直接打分;
- 行动3和行动4按 maximin:枚举对手合法选择,记录我方最差分支作为该动作分数;
- 底层价值由
cardsWeight/cardWeight提供,统一融合基础分、争夺态势和终局权重。
- 响应候选用
- 稳定输出与性能约束:
- 所有候选分数计算完后由
pickBest选最大值;若同分则按固定字典序决策,保证同输入同输出; - 通过候选去重、计数数组和短字符串运算控制复杂度,满足2s的时限。
- 所有候选分数计算完后由
- 入口与分流:主函数
-
价值函数的具体设计
- 局面统计:
boardEvalStats(board)先计算当前局面的selfScore/oppScore、selfMarks/oppMarks、resolvedMarks,作为权重输入。 - 单牌价值(
cardWeight):- 基础:
SCORE[idx] * 10; - 控制权:若该艺伎当前归对手(
board[idx]==-1)加 12,归己方加 4,未归属某一方加 7; - 计数差:若我方该艺伎计数领先加4,落后加8,持平加5;
- 终局分数:我方总分≥9 时再加
SCORE[idx]*3;对手总分≥9 且该艺伎未被我方控制时加SCORE[idx]*4; - 标记:我方标记数为3该艺伎未被我方控制时加20;对手标记数为3且该艺伎未被我方控制时加24;
- tie-break:当已接近回合后段(已用行动≥3 或已决艺伎≥5)时,追加
tieBreakTierWeight(idx)*3,其中不同牌权重为G为10,F为7,D和E为4,A、B和C为2。
- 基础:
- 组合价值(
cardsWeight):- 先累加组合内每张牌的
cardWeight; - 再加同牌协同奖励:某牌在组合中出现
copies>=2时,加copies*6。
- 先累加组合内每张牌的
- 动作价值:
- 响应动作:
evaluateChoice=myGainValue - oppGainValue (+少量局面修正); - 主动动作:
evaluateAction对行动1和行动2用直接估值,对行动3和行动4用maximin评估。
- 响应动作:
- 局面统计:
代码可复用性与需求变更
→ 📖 Q3.5(P) 请说明针对该任务,你们对 🧑💻 T2 中已实现的代码进行了哪些复用和修改。
T2中我们设计的handle函数是用来处理一个行动对于目前双方的局面的影响,而这在T3中仍然是需要的,因此我们主要是对该函数进行了复用,但在本部分,为了让代码可读性进一步增加,同时减少代码冗余度,我们将该函数进行了更加细化的拆分。同时T2中是判断一轮结束时的最终局面,而在T3中涉及到判断过程中一个节点的局面判断,在逻辑上二者区别不大,因此也复用了该逻辑,通过将行动历史按顺序拆解成一个一个单独的行动,并一次一次调用handle函数得到该行动记录对应的节点上的局面。
→ 📖 Q3.6(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
在T3中,我们要实现一个较优的策略。我认为在这里可以将评估函数与决策控制流两个功能模块彻底分离。
评估函数方面,可以通过抽离单独简单的方法,例如cardWeight,当局面或获胜规则改变,只需修改对权重或奖励的分配即可,不会影响上层策略逻辑。如果不进行有效的抽离解耦,评估逻辑和条件判定混合会让代码极难维护。
决策控制方面,需要设计获取不同方案的接口。例如在我们的实现当中,我们设计了findChoices和findActions方法。通过这些方法与接口,可以在决策控制分支里面结合评估函数选择最好的方案。当未来有需求变换时,仍然可以修改这些接口,保留主逻辑的稳健型。
软件度量
→ 📖 Q3.7(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块决策能力很强?”,尝试提出一些可能的定量分析方式或测试方式。
为了明确量度我们实现的程序模块的有效性,证明其决策能力较强,我们主要构思了以下几个维度的定量分析与测试方案:
-
基础约束测试
这也是最基本的要求。我们会让程序进行自动化对局,统计其中出现非法动作、输出异常的次数。只有不发生这些基础异常时,才能证明程序的底层逻辑和性能是稳定有效的。 -
基线对抗与胜率统计
评估决策能力最直观的定量指标就是胜率。我们可以设计一个只会“随机进行合法动作”的基础模型,或者保留我们前期开发时的“简单贪心版本”。让最终策略与这些基线进行模拟对战,统计净胜率和平均得分差。如果胜率具备压倒性优势,就能用数据说明我们的策略是有效的。 -
消融实验与参数验证
我们在评估函数中设计了多种权值(如终局额外加分、同牌协同奖励等)。为了定量证明这些设计的价值,我们可以采用控制变量法,例如屏蔽掉“控制权与争夺态势”的加分项,然后让完整版与阉割版对战。通过对比两者的胜负结果,就能说明各项启发式规则对决策能力的实际贡献。 -
泛化与稳定性分析
我们会记录程序作为先手或后手时的胜率表现差异,以及在固定多组随机种子下重复测试得分的方差。如果策略在多元化的出牌环境和先后手条件下都能稳定取得高胜率,没有明显的软肋,就可以认为其综合决策能力具有很高的鲁棒性。
通过上述多维度的批量模拟和数据统计,我们就能有理有据地衡量和说明程序模块在真实对弈环境下的决策水平。
总结
→ 📖 Q3.8(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2026-04-04 17:40
| Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| PLANNING | 计划 | ||
| - Estimate | - 估计这个任务需要多少时间 | 8 | 6 |
| DEVELOPMENT | 开发 | ||
| - Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 20 | 24 |
| - Technical Background | - 了解技术背景(包括学习新技术) | 20 | 22 |
| - Coding Standard | - 代码规范 | 4 | 3 |
| - Design | - 具体设计(确定怎么实现) | 40 | 46 |
| - Coding | - 具体编码 | 90 | 102 |
| - Code Review | - 代码复审 | 18 | 20 |
| - Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 18 | 20 |
| - Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 25 | 25 |
| REPORTING | 报告 | ||
| - Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 6 | 5 |
| - Size Measurement | - 计算工作量 | 2 | 2 |
| - Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 8 | 7 |
| TOTAL | 合计 | 259 | 282 |
→ 📖 Q3.9(I) 请写下本部分的心得体会。
Chapter.3 是整个结对项目中最具挑战、也最有成就感的一章。由于需要在限定的时间和资源内实现一个能实际给出优质决策的方案,我们放弃了起手就写复杂的博弈树或学习算法,而是讨论出一套基于“当前桌面状态的启发式”评估方案(如通过标记点位给手牌加权)。
在这个过程中,我和搭档在纸上画出了权重情况,确切推敲每个数字背后的含义才去写代码,当然,这些数字我们还是经历了一定的调参试验才最终敲定下来。这一阶段不仅锻炼了我们的算法设计能力,更考验了作为团队如何快速从需求中提炼 MVP策略进而验证的能力。
结对项目总结
结对过程回顾和反思
→ 📖 Q4.1(P) 提供两人在讨论的结对图像资料。

→ 📖 Q4.2(P) 回顾结对的过程,反思有哪些可以提升和改进的地方。
我们们回顾我们的结对过程,认为有以下几点可以进行改进:
- 角色界限与切换可以更明确:在面对复杂逻辑时,有时会不自觉地打破“驾驶员”和“领航员”的界限,变成一个人同时负责构思和敲代码,或者双方都在干相同的事,当分工没有界限时,整体效率可能就会下降。后续可以尝试更严格地定时交换角色。
- 前期设计与测试驱动的执行力度: 在实现过程中,我们有时急于让代码跑起来,脑海中有了雏形就直接上手,就会导致遗漏一些边界条件,尤其是在T3中面临复杂逻辑时。以后可以尝试更坚决地先写清伪代码、列好测试用例,再进行具体编码。
- 精力管理:我们可能为了尽力一段时间完成一个部分的内容,耗费过多精力,比如在T3上,在面临bug时可能也会死磕,但中途可能缺少一定的休息,导致大脑到后期可能有点混乱,以后可以约定一个休息时间,不需要很长,这样可以尽量保证大脑的清醒。
→ 📖 Q4.3(I) 锐评一下你的搭档!并请至少列出三个优点和一个缺点。
很高兴能和搭档合作完成本次任务!他的三个优点如下:
- 思维非常活跃敏捷:在设计 Chapter 3 的评估函数时总是能最快洞察到不同牌在局面的差异化优势。
- 注重细节和边界:每实现函数的小模块前,他都能自然想到边界情况,防止越界错误。
- 极佳的沟通素养:当我们在某个实现思路上有不同意见时,他能够很理性冷静地分析,避免了情绪化的代码争论。
一个缺点:
我的搭档在代码风格方面不是特别好,接口设计也有一点混乱,因此在我需要一定时间理解他的代码,并进行可读性的修改。
对结对编程的理解
→ 📖 Q4.4(I) 说明结对编程的优缺点、你对结对编程的理解。
我认为结对编程有如下的优缺点:
优点:
- 代码质量高、隐含的Bug少:一人编写和一人复审同时进行,很多语法错误、拼写失误或边界逻辑漏洞在刚敲出来时就被领航员发现了。
- 设计方案更优:在遇到复杂模块时,两个人可以互相验证和碰撞思路,比一个人的情况更容易想到绝妙的实现方法。
- 知识共享和学习:在项目开发中能互相学习对方优秀的代码习惯、快速命令和排错手法,两个人可以互相取长补短。
缺点:
- 需要一定时间适应:由于较少有这类经历,在前期可能就会各干各的,但这样其实就和结对编程没有太大关系了,需要花费一定时间去适应真正的结对编程做法,来真正做到一加一大于二。
我认为结对编程不仅是一台电脑前坐着两个人那么简单,它的本质是持续的、实时的代码编写和设计讨论。它可能牺牲了部分开发时间,但是换来的是更稳定、不易返工的健壮代码。我认为在极限编程和敏捷软工体系中,这是一种极其高效提升协作水平的工作方式。
代码实现提交
→ 📖 Q4.5(P) 请提供你们完成代码实现的代码仓库链接。
仓库链接

浙公网安备 33010602011771号