Skip to content

Latest commit

 

History

History
332 lines (187 loc) · 21.4 KB

question.md

File metadata and controls

332 lines (187 loc) · 21.4 KB

结对项目:博客问题清单

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

Chapter.1 缠结的字节码(Bytecode Complex)

引入

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

2024.3.28 19:15

→ 📖 Q1.2(P) 根据之前学习的专业课和各类开发经验,回答:为什么原生应用相较 Web app 更有快的印象?这样的“速度”又是如何达成的?

数据的存储位置。原生app只需要从本地加载数据,web app需要从服务器请求、发送数据,受网络传输速度和数据分布式存储的限制。

web app 具有多平台运行的需求,web 语言的开发更侧重于拓展 web app 的可拓展性,需要降低web的开发难度。不同执行环境带来的额外适配成本、跨平台软件构建的开销复杂化了开发和分发,占用了提高app速度的成本。

→ 📖 Q1.3(P) “要让 Web 生态、或者说一种雄心勃勃的软件运行环境的未来,受益于既有的‘原生’软件开发模式、技术栈和工具链,我们还缺少什么?”——你们的答案是?

可以加强web开发语言与不同系统的适配性,通过统一的中间语言衔接web开发和原生软件开发环境。

结对过程

→ 📖 Q1.4(I) 作为本项目的调查:请如实标注在开始项目之前对 Wasm 的熟悉程度分级,可以的话请细化具体的情况。

I. 没有听说过;

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

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

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

I. 没有听说过;

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

我们使用的编程语言是c++,c++的wasm需要使用emscripten库导出js和wasm文件,然后从js中导出函数,与测试文件交互。

第一个难题是从js文件中导出函数。

c++的函数不能在简单的编译后,通过 import 语句导入。经过我们的调研,最后得到的解决方案是,在命令行中声明要导出的函数,并表明导出的形式。然后在js文件中通过 Module 方法的 cwrap 函数对c++函数重新封装,重新声明函数,设置函数的参数列表和返回值,才能完成调用。

第二个难题是js文件中如何调用c++函数,输入预定的数组数据。

c++函数的参数是一个整数指针,但是在js文件中无法直接将数组传输进去。在我们的方案中,需要为数组先在栈上分配空间,将数组复制到栈上,然后将栈的指针作为参数传递给函数。也就是说,对于涉及数组操作的函数,需要在js文件中自主进行内存管理。

我们解决问题的过程中参考了很多博客、官网教程,也咨询了很多其他同学的解决方案。模块一的主要难点就在于 c++ 和 js 的交互上,是我们花了最多时间的部分。

编程语言选择

自选的编程语言
→ 📖 Q1.6.X.1(P) 选择该编程语言的原因是?

因为相比于其他语言,我们对c++比较熟悉。且c++对wasm的支持相对完善。

→ 📖 Q1.6.X.2(P) 为了完成相关编程任务,进行了哪些操作、使用了怎样的工具链?简要描述相关工具做了什么。

在 c++ 的 web asembly 转换中,我们使用了 emsdk 库导出对应的 js 和 wasm 文件,然后从js中导出函数,与测试文件交互。

首先,下载 emsdk 库,配置并加载emcc环境。

参考博客:https://blog.csdn.net/GISuuser/article/details/118310452?spm=1001.2014.3001.5501

然后,我们参考官网指引,使用emcc指令编译c++文件,生成js和wasm。

我们查找了 emcc 官网 > porting > 连接c++和js > 代码交互 > [使用ccall/wrap](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-ccall-cwrap),并参考gpt,发现以下操作可行:

在cpp文件中,用 extern 'C' {} 包裹要导出的函数。

命令行输入: emcc bocchiShutUp.cpp -o function.js -s EXPORTED_FUNCTIONS="['_bocchiShutUp', '_malloc', '_free']" -s EXPORTED_RUNTIME_METHODS="['ccall', 'cwrap']" -s EXPORT_ES6=1

-o 标识编译输出的文件名

-sEXPORTED_FUNCTIONS 标注出需要导出的c++函数

-sEXPORTED_RUNTIME_METHODS=ccall, cwrap 这里的ccall和cwrap是之后与 test.js 交互时需要用到的函数

-EXPORT_ES6 为了能够从js文件中顺利导出 module 添加的必要参数

然后修改test.js文件内容,导入相应函数:

import Module from "./t1_cpp/function.js"; //导入Module方法

var RealModule = await Module() //异步调用Module
var callBocchiShutUp = RealModule.cwrap('bocchiShutUp', 'number', ['number', 'number', 'number']) //用cwrap函数导入写好的c++函数,设置函数的参数列表和返回值

// 将c++和js的指针参数对齐
function bocchiShutUp(flag, array, size) {
    // 在js中对数组进行内存管理,手动分配内存,设置数组指针
    const bytesPerElement = Int32Array.BYTES_PER_ELEMENT;
    const numBytes = size * bytesPerElement;
    const ptr = RealModule._malloc(numBytes);
    
    let heapView = new Int32Array(RealModule.HEAP32.buffer, ptr, size);
    heapView.set(array);
    
    // 调用c++函数
    const result = callBocchiShutUp(flag, ptr, size);
    
    RealModule._free(ptr);

    return result;
}

总结

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

2024.3.28 22:00 第一次结对编程结束,没有完成全部工作

第二次接着解决问题,从2024.4.1 16:00,到 2024.4.1 16:45 完成第一部分全部工作。

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

Chapter.2 石子翻滚,晨光降临你身(Rock'n Roll, Morning Light Falls on You)

结对过程

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

2024.4.1 17:00

→ 📖 Q2.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 135 180
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 10 5
- Technical Background - 了解技术背景(包括学习新技术) 5 5
- Coding Standard - 代码规范 5 5
- Design - 具体设计(确定怎么实现) 5 5
- Coding - 具体编码 60 30
- Code Review - 代码复审 15 5
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 5 5
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 30 120
REPORTING 报告 30 30
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 15 15
- Size Measurement - 计算工作量 5 5
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 10 10
TOTAL 合计 170 215

我们的编程进行的很顺利。在测试阶段,对于可能出现的异常行为,枚举可能产生的错误,并自行捏造数据。但是对于如何测试程序的正确性,我们罗列了一些解决方法,最后还是决定通过编写一个随机数据生成器,通过两个程序的相互对拍验证正确性。我们使用python语言,以相同的游戏逻辑复现了游戏步骤,通过生成随机数模拟出游戏行为,作为正常情况的输出。在 c++ 测试中,读取 python 生成的数据,并调用c++函数,验证两个程序的输出是否一致。我们对10000条游戏步骤数据进行了测试,保证了程序的正确性。因此,我们的测试阶段比预计多花了很多时间,相当于又实现了一遍游戏逻辑,还在原有的基础上开发debug模式,工作量比预期大很多。

测试

→ 📖 Q2.3(P) 请说明针对该任务,你们设计和实现测试的方法及过程,包括但不限于:出于对需求的哪些考虑设计了哪些测试用例、如何评估所设计测试的有效性 等等。

异常行为:拿空格子,结束仍操作,操作者错误;这部分采用手工捏造数据

正常行为:写 python 数据生成脚本

统一在 test.cpp 利用 assert 进行测试。

对于脚本生成是否正确,我们首先对生成的几局比赛进行人工核验,再者对于每次的错误,我们都会从脚本和实现函数两个方面去纠错。

→ 📖 Q2.4(I) 请说明单元测试对软件开发的作用。

总结

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

2024.4.1 21:30

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

Chapter.3 十二进制的黄昏(Dusk of the Duodecimal System)

结对过程

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

2024.4.2 19:00

→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 200 210
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 20 10
- Technical Background - 了解技术背景(包括学习新技术) 30 30
- Coding Standard - 代码规范 5 5
- Design - 具体设计(确定怎么实现) 15 20
- Coding - 具体编码 60 80
- Code Review - 代码复审 15 15
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 15 10
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 40 40
REPORTING 报告 30 100
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 15 50
- Size Measurement - 计算工作量 5 15
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 10 35
TOTAL 合计 235 315

代码可复用性与需求变更

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

T2 的函数功能是对输入的操作序列,判断是否违规,输出最后的得分。需要记录棋盘的中间状态,根据操作一步步推导,最终输出结果数据。

该任务的输入参数与T2相同,去除了在每一步判断操作合法的要求,并以数组的形式返回棋盘的中间状态。

因此,对于T2的代码,只需要去除中间状态的违规判断逻辑,修改最后的输出结果,并输出中间状态即可。对于T2的状态推演、下一步执棋判断、是否结束棋局的判断,都可以复用。

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

需求建模和算法设计

→ 📖 Q3.5(P) 请说明你们如何建模这一需求。

对于一个现有的棋盘状态,我们需要给出尽可能有利于己方的播撒决策。这是一个每次操作可见的完美博弈,主要的解法就是模拟。意思就是,棋盘策略数量有限,理论上可以通过罗列全部选择方法,比较每种决策的得分,做出最优选择。但实际的硬件算力无法支持这个规模的计算。因此与其他棋类博弈类似,需要借助搜索算法,优化搜索路径,量化搜索结果,缩小决策空间,得出局部最优解。如何高效搜索就是核心优化点。

对于一个连续的策略,不同的选择相当于树的不同分支,因此对决策的模拟本质上等同于一个博弈树的搜索算法。树的叶子结点是棋局的结束状态,因此树的深度有限。题目中有计算的时间限制,所以我们不能对所有分支都搜到根节点,需要限制搜索的层数,即限制棋盘模拟的步数。对于一个策略序列,可以通过最终状态或者中间状态的分数差距作为得分,量化策略的优劣。

搜索算法的流程如下图所示:

image-20240406032442102

我们需要实现的函数有:

  1. 棋盘的状态推进函数
  2. 判断是否结束/是否到达指定层数
  3. 对一个棋局(中间状态或结束的棋局)评分函数
  4. 策略入口,统计最优策略的函数
→ 📖 Q3.6(P) 请说明针对该任务,你们采取了哪些策略来取得更大的净胜棋数,如何编程实现这些策略。
  1. 极大极小搜索 MiniMax

播棋的执棋总体上是交替的,因此两步之间执棋方交替。以当前执棋方的视角来看,自己会选评分最大的策略,对方会选评分最小的策略。这种局面符合极大极小搜索的算法。因此,我们需要区分每一步的决策者和执棋方,交替使用两种不同的对抗策略。

(下图的 max 和 min标反了)

图片

  1. $\alpha\beta$剪枝策略

$\alpha\beta$剪枝是对极大极小搜索的优化。在每个棋手追求最大化或最小化收益的时候,如果某个节点的值超过了一定范围,就可以不对他进行下一步的搜索。

比如,对于一步最小决策,它的下一步为最大决策。如果它的第一个分支中,下一步决策中的最大值为8,因此它的决策值为8。如果最小决策的第二个分支中,它的子决策中有一个决策值为9,则下一步最大决策值必然大于等于9,因此这个分支必定不如第一个分支,肯定不是最小分支,因此第二个分支不需要再遍历完所有决策。

这个节点值范围通过$\alpha$,$\beta$值跟随树的深度优先搜索层层下传,超过范围时,结束该层的遍历,达到剪枝的效果。

  1. 启发式奖励因子

我们研究了播棋的规则和玩法,发现在两个人的对弈中有一些启发式策略,包括:

  1. 先手第一步取3号洞,第二步取6号洞
  2. 提防取子操作
  3. 尽量将棋子的最终落点放在计分洞,促成己方的连续操作
  4. 尽量保持6号格子的清空,以便连续操作
  5. 残局时,如果对方启动中包含大量棋子,需要拖慢进度逼迫对方播撒棋子。

因为工作量和时间关系,我们只按照第五条规则,在评估函数中加入奖励算子,改变决策偏向。

理论上,以上策略都能通过改变评估函数实现。但是评估函数的修改方式和影响力度都需要仔细思考,并通过测试和时间检验有效性,复杂度比想象的高。因此我们只实现了一条比较容易实现的规则,效果还不错。

软件度量

→ 📖 Q3.7(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块对弈能力很强?”,尝试提出一些可能的定量分析方式。
  • 让程序和自己对弈,确定先手净胜棋数的稳定性,保证程序可靠
  • 人机对弈:我们增加了人工输入端口,可以让人工来与机器博弈,这样能让我们对所设计的算法有个初步的判断
  • 更新前后博弈:将更新前后的算法进行博弈,根据得分判断性能是否优化
  • 找现有其他程序(GitHub仓库或者其他同学的代码)与我们的程序对弈,统计先后手的净胜棋数

总结

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

2024.4.2 22:30完成代码编写,结束第一阶段。

2024.4.6 20:40开始第二阶段:代码测试和编写文档

2024.4.6 22:00完成T3。

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

结对项目总结

结对过程回顾和反思

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

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

本次结对作业中,整体编写代码和测试的过程都没有什么疑义,写的速度很快,两个人在代码逻辑上没有什么分歧,也即时发现了很多问题,比一个写产生的bug更少,确实提高了效率。

但是本次结对中,代码的测试程序和测试手法不够全面和完善,仍然有少部分的bug和问题从T2被遗留到了T3。虽然在T2中,我们既用了自动生成的测试数据测试正确情况,又针对违规情况单独分析测试,但还是没有完全覆盖所有样例情况。比如,一局的结束既可以是执棋方把自己的棋盘清空,也可以是执棋方通过取子操作把对方的棋盘清空;我们在T2中没有考虑清空对方棋盘的情况,以至于在T3的搜索算法中出现负权重,给出异常值-1。我们将大部分精力放到了代码编写上,对规则的理解和测试的设计还不够完善。

本次结对主要的难点在于 T1 的wasm集成和T3的搜索算法设计。这些需要事先调研的部分我们在结对现场没办法很完善的完成(因为结对时我们处于神经高度紧张的状态,没办法静下心来花很多时间调研),所以我们T1的wasm是在结对时间之外完善补充的;T3的搜索算法也不算是最优解,性能和决策方法相对不突出,还有很多可以完善的空间。因此结对前应该先自行对项目有一个大致的理解和基础资料的搜寻,可以事先完成与代码逻辑无关的部分,提高结对编程的效率。

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

对结对编程的理解

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

代码仓库

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

https://github.com/XingZiiii/SEPairProgramming