🐍、移動
这个作业属于哪个课程 | 2025春软件工程 |
---|---|
这个作业的要求在哪里 | [P] 结对项目:影蛇舞 |
我在这个课程的目标是 | 开发可以实际投产的软件项目 |
这个作业在哪个具体方面帮助我实现目标 | 练手,完成一个小型项目 |
Chapter.0 Belua multorum es capitums.(你是多首的怪物。)
引入
→ 📖 Q0.1(P) 请记录下目前的时间。
2025 年 3 月 25 日 16:00
调查
→ 📖 Q0.2(I) 作为本项目的调查:请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。
I. 没有听说过;
II. 仅限于听说过相关名词;
III. 听说过,且有一定了解;
IV. 听说过,且使用 Wasm 实际进行过开发(即便是玩具项目的开发)。
II. 仅限于听说过相关名词;
总结
→ 📖 Q0.3(P) 请记录下目前的时间。
2025 年 3 月 25 日 16:10
Chapter.1 不畏迷茫,只管前进。(迷子でもいい、前へ進め。)
结对过程
→ 📖 Q1.1(P) 请记录下目前的时间。
2025 年 3 月 25 日 16:10
→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
PLANNING | 计划 | ||
- Estimate | - 估计这个任务需要多少时间 | 20 | 20 |
DEVELOPMENT | 开发 | ||
- Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 2 | 2 |
- Technical Background | - 了解技术背景(包括学习新技术) | 2 | 2 |
- Coding Standard | - 代码规范 | 0 | 0 |
- Design | - 具体设计(确定怎么实现) | 1 | 1 |
- Coding | - 具体编码 | 10 | 10 |
- Code Review | - 代码复审 | 4 | 4 |
- Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 1 | 1 |
- Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 0 | 0 |
REPORTING | 报告 | ||
- Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 0 | 0 |
- Size Measurement | - 计算工作量 | 0 | 0 |
- Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 0 | 0 |
TOTAL | 合计 | 20 | 20 |
- (C++)跟随emscription官网事例安装环境(尝试brew,似乎brew的版本有问题)
(Rust) 参考了题面、示例代码和 the Rust and WebAssembly Book
测试
→ 📖 Q1.3(P) 请说明针对该任务,你们设计和实现测试的方法及过程,包括但不限于:出于对需求的哪些考虑设计了哪些测试用例、如何评估所设计测试的有效性 等等。
- 示例代码提供的测试用例
- 边界条件的测试用例:蛇头在边界/角落、果子在边界/角落、果子在蛇的不同方位处
- fuzzer,随机生成大量输入,测试蛇移动的合法性和正确性
→ 📖 Q1.4(I) 请说明单元测试对软件开发的作用。
最小化检测功能的正确性和完整性,可以防止代码已经膨胀到一个难以溯源的程度时发生因为小问题导致的bug。
在用copilot的情况下尤其重要
别问我为什么要加这句
总结
→ 📖 Q1.5(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2025 年 3 月 25 日 16:30
分两段编写了 C 和 Rust 的解法,各耗时约 10 分钟。
→ 📖 Q1.6(I) 请写下本部分的心得体会。
太简单了!时间都花在了和编译器斗争上
年轻的wasm初学者还不知道这句话代表了什么
Chapter.2 即使迷茫,也要前行。(迷子でもいい、迷子でも進め。)
结对过程
→ 📖 Q2.1(P) 请记录下目前的时间。
2025 年 4 月 1 日 16:30
→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
PLANNING | 计划 | ||
- Estimate | - 估计这个任务需要多少时间 | 120 | |
DEVELOPMENT | 开发 | ||
- Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 10 | 20 |
- Technical Background | - 了解技术背景(包括学习新技术) | 0 | 0 |
- Coding Standard | - 代码规范 | 0 | 0 |
- Design | - 具体设计(确定怎么实现) | 10 | 20 |
- Coding | - 具体编码 | 30 | 45 |
- Code Review | - 代码复审 | 5 | 10 |
- Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 5 | 5 |
- Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 1 | 1 |
REPORTING | 报告 | ||
- Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 0 | 5 |
- Size Measurement | - 计算工作量 | 0 | 0 |
- Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 0 | 0 |
TOTAL | 合计 | 61 | 106 |
(C++)没有额外内容
(Rust) 相比 T1 没有额外的参考内容。
代码可复用性与需求变更
→ 📖 Q2.3(P) 请说明针对该任务,你们对 🧑💻 T2
中已实现的代码进行了哪些复用和修改。
复用了项目目录结构,方向、坐标等基础数据结构的定义
复用封装好的蛇,坐标等
基本上没有修改
→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
设计时代码采用模块化的思想,另外把整个t1-t3的接口一起编译到了一个库中(虽然后来因为引入了模型权重又分开了)。测试为每个问题分别提供了makefile,并且在根目录makefile提供了target。从T2开始就在考虑用强化学习做T3,所以在这里就已经开始设计模型,并且用T2进行了测试。
头脑风暴环节
**→ 📖 Q2.5(P) **只吃一个食物可满足不了贪吃蛇的欲望,请一起思考并简述以下场景中贪吃蛇的策略:
在 🧑💻 T2
的基础上,场地里不再是只有 1 个果子,而是总共有 n 个果子 (1 < n < 10 ),果子随机分布在场地中且不会刷新,保证不与障碍物重叠,保证每个果子均可达,且至少存在一条成功吃掉所有果子的路线,其余条件保持不变,请你找出一条吃完所有果子的行动路径。
因为至少存在一条成功吃掉所有果子的路线,则至多存在 1 个果子,若在吃掉其他全部 $n - 1$ 个果子之前先吃掉这个果子,则会导致其他果子不可达(蛇进入无法回头的死胡同)。可以分三种情况讨论策略:
-
场地上只有一个果子,可以直接吃掉这个果子
-
如果存在这样的果子,我们先按某种顺序吃掉其他的 $n - 1$ 个果子,然后再吃掉这个果子
-
如果不存在这样的果子,我们可以任意选择一个顺序吃掉所有果子。
在 2 的情况下,考虑除了这个果子以外的 $n - 1$ 个果子,它们也符合上述条件,所以我们可以递归地规划出吃掉所有果子的顺序。
然后,根据规划好的顺序,可以利用已有的算法追踪按顺序吃掉每个果子的路径。
通过以下算法选择处理策略:
- 若果子数量为 1,按情况 1 处理。
- 使用有方向标记的 BFS 算法来判定果子的可达情况:
- 访问一个格子,将当前格子和蛇头的方向加入已访问集合
- 将周围的三个格子和方向加入队列
- 循环处理未访问过的格子和方向组合,直到队列为空。
- 根据已访问集合遍历果子坐标:
- 由于所有果子均可达,每个果子的坐标在已访问集合中至少有一个方向的标记。
- 如果一个果子的的坐标在已访问集合中有两个或更多个方向的标记,这个果子可以按任意顺序吃掉。
- 如果一个果子的的坐标在已访问集合中只有一个方向的标记,将其坐标和方向加入一个集合。
- 若集合为空,按情况 3 处理。
- 循环处理集合:
- 从集合中任意选择一个元素(坐标, 方向)
- 对(坐标, 方向)元组,循环检查该坐标在相反方向的格子:
- 若反方向的格子在已访问集合中不存在,或者有两个或更多个方向的标记,停止循环
- 反方向的格子在已访问集合中只有一个方向的标记,取出该(坐标, 方向)元组
- 若集合中包含该元组,将其删除
- 对该元组重复上述处理
- 若集合中仅剩一个元素,停止处理,否则从集合中删除选择的元素。
- 集合中仅剩的元素对应的果子作为最后吃掉的果子,按情况 1 处理。
总结
→ 📖 Q2.6(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
2025 年 4 月 4 日 10:38
中间间隔若干小时
两版本总用时约2小时
→ 📖 Q2.7(I) 请写下本部分的心得体会。
这里考虑了很久用A*还是Dijkstra,最后还是用了Dijkstra,因为状态并没有那么简单(蛇头和蛇第二节身子的位置都需要考虑),所以Dijkstra似乎并不是最好的实现,而A*又相当适配这个问题(不要求最优)
结果还是用了Dijkstra
Chapter.3 这就是我的前进、到我出场了!!!!!(It's MyGO!!!!!)
结对过程
→ 📖 Q3.1(P) 请记录下目前的时间。
→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
- 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
- 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages | 个人软件开发流程 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
PLANNING | 计划 | ||
- Estimate | - 估计这个任务需要多少时间 | 360 | |
DEVELOPMENT | 开发 | ||
- Analysis & Design Spec | - 需求分析 & 生成设计规格(确定要实现什么) | 60 | 20 |
- Technical Background | - 了解技术背景(包括学习新技术) | 30 | 120 |
- Coding Standard | - 代码规范 | 0 | 0 |
- Design | - 具体设计(确定怎么实现) | 30 | 240+ |
- Coding | - 具体编码 | 120 | 240 |
- Code Review | - 代码复审 | 30 | 30 |
- Test Design | - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) | 15 | 30 |
- Test Implement | - 测试实现(设计/生成具体的测试用例、编码实现测试) | 30 | 60 |
REPORTING | 报告 | ||
- Quality Report | - 质量报告(评估设计、实现、测试的有效性) | 0 | 60 |
- Size Measurement | - 计算工作量 | 0 | 0 |
- Postmortem & Process Improvement Plan | - 事后总结和过程改进计划(总结过程中的问题和改进点) | 0 | 20 |
TOTAL | 合计 | 315 | 820+(很多时间花在思考这个上面,难以度量) |
- 跟AI交流确定采用DQN,查询相关资料确定可以实现
- 用python实现了一个版本,效果不好
- 参考开源范式重新实现,训练一直没有效果,在这里反复debug最后发现框架编写时漏了一个小错误
- 成功训练可用的agent,一边实验和优化参数,一边处理推理的问题
- 尝试onnxruntime,无同步api,无法正确调用
- 尝试rust版binding,编译陷入依赖地狱,放弃
- 尝试cpp版onnxStream,一大坨依赖,难以编译,放弃
- 最后选择手写cpp推理,此处略过若干版重构
- 推理框架和模型参数读入问题绑定,同时处理
- 基于vector的推理框架,陷入vector嵌套地狱,编译极慢,重写
- 重新wrap了Tensor,处理了若干难以追溯的问题(包括但不限于内存溢出,爆栈,错误实现,错误传参),由于引入了一个巨大的参数矩阵(硬编码),期间vscode屡次卡死,仰卧起坐
总结
- 不要太相信copilot,一定进行单元测试
- 简单的或许是最好的,不要太相信助教的神秘url
需求建模和算法设计
→ 📖 Q3.3(P) 请说明你们如何建模这一需求。
将场地建模为 $n \times n \times s$ 的向量,其中 $n$ 为场地边长,$s$ 为每个格子的状态数量。格子可能的状态包括空地、蛇头、蛇身、蛇尾、敌方蛇头、敌方蛇身、敌方蛇尾、果子。算法接受一个场地作为输入,输出 0-3 的值表示方向。
→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么避免死亡?怎么吃到更多果子?如何编程实现。
使用 DQN 算法训练模型,设置奖励函数,为果子和正确的方向设置奖励,为死亡和错误的方向设置惩罚。通过调整网络结构和奖励函数优化决策。
软件度量
→ 📖 Q3.5(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块对弈能力很强?”尝试提出一些可能的定量分析方式。
与基于规则的或其他基于神经网络的多蛇对弈算法同台竞技,统计规定回合后最终得分、死亡率、K/D 比等统计数据。
总结
→ 📖 Q3.6(P) 请记录下目前的时间,并根据实际情况填写 附录A:基于 PSP 2.1 修改的 PSP 表格 的“实际耗时”栏目。
开发最终持续一周左右。大约经过 8 小时设计出最初可用版本,8 小时优化策略,12 小时迁移到 WebAssembly 平台(期间断断续续开发若干次,难以统计)
→ 📖 Q3.7(I) 请写下本部分的心得体会。
本章节出现的问题(并且拖累了开发进度)包括且不限于
-
写了训练框架、但是不管用
-
根据范式改进了训练框架,但是不管用
-
尝试调参,修改网络结构,反复检查loss和梯度,反复更换激活函数和损失函数,反复检查训练代码和输入建模,无果
-
发现是记忆回放并没有真正保存每一次的状态,而是保存了状态的引用(导致实际保存的训练数据一直在变),心态崩溃x1
-
修复,训练,测试,皆大欢喜(并不)
-
尝试用onnxruntime把模型移植到js,只提供了异步接口,助教提供方案1失败
-
尝试Rust开源rt,依赖地狱编译不成功,助教提供方案2失败
-
尝试cpp开源库,文档不完善+引用了一个超大的依赖,如果走这个方案要写一个很复杂的CMakeLists,助教提供方案3失败,心态崩溃x2
MacOS下开发cpp导致的(为了编译到wasm,cmake生成的编译命令中不能携带-arch,然而这在MacOS下我是没能发现好的方法,并且这并不是我遇到的唯一一个问题)
为什么不用windows?那玩意还不如mac
-
重整旗鼓,手写推理框架,(此处省略若干与编译器和cpp语法斗智斗勇的情节),终于成了
结对项目总结
结对过程回顾和反思
→ 📖 Q4.1(P) 提供两人在讨论的结对图像资料。
→ 📖 Q4.2(P) 回顾结对的过程,反思有哪些可以提升和改进的地方。
仔细审查copilot的代码,做单元测试
→ 📖 Q4.3(I) 锐评一下你的搭档!并请至少列出三个优点和一个缺点。
- UP
- 代码能力强,善于运用工具
- 有能力修复依赖关系和编译错误
- 熟悉多种编程范式
- DOWN
- Rust写多了
- Rust写多了
对结对编程的理解
→ 📖 Q4.4(I) 说明结对编程的优缺点、你对结对编程的理解。
没什么用,远远不如一人写另一人review
我对结对编程的理解:我当pilot看copilot的代码