[P] 结对项目:影蛇舞

项目 内容
这个作业属于哪个课程 2025年春季软件工程(罗杰、任健)
这个作业的要求在哪里 [P] 结对项目:影蛇舞
我在这个课程的目标是 学习软件工程知识,通过团队协作开发一个具备实际应用价值的软件,从需求分析、设计、开发到测试和部署,完整经历软件开发生命周期,提高工程实践能力。
这个作业在哪个具体方面帮助我实现目标 通过与他人合作开发,完成一个项目,提高代码能力、代码审查能力和沟通能力

结对项目:博客问题清单

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

Chapter.0 Belua multorum es capitums.(你是多首的怪物。)

引入

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

2025.3.26 3:00

调查

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

I. 没有听说过;

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

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

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

I.

总结

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

2025.3.26 3:34

Chapter.1 不畏迷茫,只管前进。(迷子でもいい、前へ進め。)

结对过程

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

2025.3.26 3:40

→ 📖 Q1.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 85 55
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 4 10
- Technical Background - 了解技术背景(包括学习新技术) 4 5
- Coding Standard - 代码规范 10 3
- Design - 具体设计(确定怎么实现) 10 2
- Coding - 具体编码 25 20
- Code Review - 代码复审 10 5
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 10 5
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 12 5
REPORTING 报告 30 20
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 8 10
- Size Measurement - 计算工作量 5 5
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 12 5
TOTAL 合计 120 80
  1. 我们首先分析任务文档,发现蛇只有四节,那么蛇无论是向哪一个方向运动,均不可能撞到自己的身子(除非扭脖子自杀)。那么接下来只需要计算蛇头到果子的曼哈顿距离然后朝向移动即可。
    接下来就是编码实现和实现自动测试。

测试

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

由于问题的简单性,我们直接选择采用自动生成测试样例进行测试。
分别编码实现generateSnakegenerateFood,然后调用课程组的greedy_snake_fn_checker

let snake = generateSnake();

let food = generateFood(snake);

let result = greedy_snake_fn_checker(snake, food);
→ 📖 Q1.4(I) 请说明单元测试对软件开发的作用。
  1. 提高代码质量:及时发现并修复函数级别的逻辑错误或边界情况问题,同时帮助个人更加了解模块的输入输出方式。

  2. 支持重构:修改或优化代码结构时,有单元测试作保障,可以避免引入新错误。

  3. 降低维护成本:随着项目变大,有系统的单元测试可以快速定位问题,减少查错时间。

  4. 提高开发效率:虽然编写测试需要时间,但可以节省后期调试和修复 Bug 的成本,总体的收益是正向的。

总结

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

2025.3.26 4:40

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

整体难度不大,比较轻松,主要是掌握了新的技术栈,用于本次作业的入手

Chapter.2 即使迷茫,也要前行。(迷子でもいい、迷子でも進め。)

结对过程

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

2025.3.26 5:00

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

代码可复用性与需求变更

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

保留了对于防止蛇头撞到边界的代码逻辑,其余代码均有更改。

→ 📖 Q2.4(I) 请说明在编码实现时,可以采取哪些设计思想、考虑哪些设计冗余,来提高既存代码适应需求变更的能力。
  1. 模块化设计:把功能拆分成清晰的模块(组件),使每个模块尽可能职责单一、低耦合、高内聚。

  2. 面向接口编程:对外暴露接口,不直接依赖具体实现。

  3. 扩展与合理冗余:留好可扩展的空间,保留参数、预定义接口或 hook 点,即使当前不需要,也为将来做准备。

  4. 功能参数化:把可能变化的行为设计成可配置参数,如通过配置文件控制逻辑分支,减少硬编码,比如不同的果子或者地图大小等。

头脑风暴环节

**→ 📖 Q2.5(P) **只吃一个食物可满足不了贪吃蛇的欲望,请一起思考并简述以下场景中贪吃蛇的策略:

在 🧑‍💻 T2 的基础上,场地里不再是只有 1 个果子,而是总共有 n 个果子 (1 < n < 10 ),果子随机分布在场地中且不会刷新,保证不与障碍物重叠,保证每个果子均可达,且至少存在一条成功吃掉所有果子的路线,其余条件保持不变,请你找出一条吃完所有果子的行动路径。

寻找在“死角”的果子。因为至少存在一条能够吃完所有果子的路径,因此最多只存在一个果子,到达该果子的位置时,不可达其他果子。模拟计算蛇头在所有果子位置对其他果子的可达性,可知道死角果子,最后选择这个果子到达即可。

总结

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

2025.3.26 9:56

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

整体思路不复杂,是之前算法和数据结构之类的课见过的,但是代码能力真的一言难尽,写起来很慢,而且也不少bug,还是要好好提升代码能力!

Chapter.3 这就是我的前进、到我出场了!!!!!(It's MyGO!!!!!)

结对过程

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

2025.4.2 3:16

→ 📖 Q3.2(P) 请在完成任务的同时记录,并在完成任务后整理完善:
  1. 浏览任务要求,参照 附录A:基于 PSP 2.1 修改的 PSP 表格,估计任务预计耗时;
  2. 完成编程任务期间,依次做了什么(比如查阅了什么资料,随后如何进行了开发,遇到了什么问题,又通过什么方式解决);
Personal Software Process Stages 个人软件开发流程 预估耗时(分钟) 实际耗时(分钟)
PLANNING 计划 5 5
- Estimate - 估计这个任务需要多少时间 5 5
DEVELOPMENT 开发 300 420
- Analysis & Design Spec - 需求分析 & 生成设计规格(确定要实现什么) 20 20
- Technical Background - 了解技术背景(包括学习新技术) 20 40
- Coding Standard - 代码规范 10 5
- Design - 具体设计(确定怎么实现) 30 40
- Coding - 具体编码 180 280
- Code Review - 代码复审 10 10
- Test Design - 测试设计(确定怎么测,比如要测试哪些情景、设计哪些种类的测试用例) 10 10
- Test Implement - 测试实现(设计/生成具体的测试用例、编码实现测试) 20 15
REPORTING 报告 55 45
- Quality Report - 质量报告(评估设计、实现、测试的有效性) 30 25
- Size Measurement - 计算工作量 10 10
- Postmortem & Process Improvement Plan - 事后总结和过程改进计划(总结过程中的问题和改进点) 15 10
TOTAL 合计 360 470
  1. 针对题目,我们进行了大量检索,很快发现了两条可行的路径。
    • 传统优化算法
    • 强化学习
      由于我们对强化学习并不熟悉,所以计划对本任务分两部分实现:即先实现传统优化算法,如果还有时间可以以传统算法为base算法,实现训练。

需求建模和算法设计

→ 📖 Q3.3(P) 请说明你们如何建模这一需求。
  • 传统算法:蛇蛇要完成避障和得分两个任务,同时争抢果子会导致死亡。那么很显然,我们复用第二问的BFS算法,可以在每个回合计算出所有果子到自己的绝对距离和到其他所有蛇的绝对距离。由此得到一个二维表。
    根据二维表,我们设计了一个简单的迭代算法。
  1. 刚开始迭代次数为0,计算我的蛇是否距离一个果子是最近的,如果有直接返回
  2. 如果没有呢?我们用相同的做法处理所有其他蛇,可以得到其他蛇的候选果子(候选果子是距离本蛇排名为【迭代次数+1】的果子),标记有候选果子的蛇,他们的迭代次数+1
  3. 得到其他蛇的候选果子之后,我们可以确定他们回去到这个地方吃果子,因此可以我们选择候选果子更新所有有候选果子的蛇到其他果子的距离,(不要修改之前的候选果子)
  4. 迭代次数+1
  5. 直到迭代次数达到20,或者产生一个有效的果子
  6. 如果迭代次数到达上限,直接返回一个safeMove

本算法的思想就是寻找相对距离最近的果子,那如果一开始没有怎么办呢?很简单,我们假设所有的蛇都会自然的选择相对距离最近的果子,那么一旦其他蛇选定一个果子为【候选果子】,他接下来的移动就一定会经过该果子,那么据此更新他到其他果子的距离。同时标记这个已经被选掉的果子。在第二次迭代中,本蛇如果还是没有【候选果子】,再次选择其他蛇的【候选果子】,但是这个时候其他蛇肯定不能选择最近的果子(第一次已经选过了),那么这次【候选果子】就是第二近而且相对自己比其他所有蛇更近的果子。以此类推可以得到每个蛇的【迭代标记】,【候选果子】就是距离自己【迭代标记+1】近,且相对距离自己最近的果子。

我们在【候选果子】的判断中,选择如果大家都离一个果子一样近,选择他。即一种容易同归于尽的策略。我们的想法是,要进入4v4,首先需要通过1v1,1v1的同归于尽是没有负担的。如果对方采用保守的策略,就会被我们一直得分,如果采用激进的策略大不了同归于尽。

  • 强化学习:我们实现了可跑通的强化学习,但最终效果较差,但在这里进行一个简要介绍:
    1.我们设立了一个DQN网络

    class DQN(nn.Module):
      def __init__(self, input_shape, num_actions):
          super(DQN, self).__init__()
          self.conv = nn.Sequential(
              nn.Conv2d(input_shape[0], 32, kernel_size=3, stride=1),
              nn.ReLU(),
              nn.Conv2d(32, 64, kernel_size=3, stride=1),
              nn.ReLU()
          )
          conv_out_size = self._get_conv_out(input_shape)
          self.fc = nn.Sequential(
              nn.Linear(conv_out_size, 256),
              nn.ReLU(),
              nn.Linear(256, num_actions)
          )
    
      def _get_conv_out(self, shape):
          o = self.conv(torch.zeros(1, *shape))
          return int(np.prod(o.size()[1:]))
    
      def forward(self, x):
          conv_out = self.conv(x).view(x.size(0), -1)
          ans = self.fc(conv_out)
          # print(ans)
          return self.fc(conv_out)
    

    训练这个网络,选取最大的Q值

    2.对于输入的张量的建模如下:

    ```python
    返回每条蛇的观察状态,每个状态为 shape: (3, grid_size, grid_size)
      通道0: 所有蛇的身体(包括蛇头和身体,标记为1)
      通道1: 蛇头信息:对于当前蛇,头部标记为2,其它蛇头标记为1
      通道2: 食物位置(标记为1)
    ```
    

    3.由于我们使用的是assemblyscript语言,无法实现深度学习,采用自己实现卷积,全连接层等,同时采用数组形式,硬编码输入参数,进行迁移
    4.设置的奖励函数:

        def _calculate_reward(self, snake_idx):
        """动态奖励:鼓励靠近食物,惩罚远离,超过步数上限则惩罚,吃一个果子奖励20分,撞墙惩罚40分"""
        head = self.snakes[snake_idx][0]
        reward = 0.1  # 基础存活奖励
        if self.foods:
            current_dist = min([abs(head[0] - f[0]) + abs(head[1] - f[1]) for f in self.foods])
            prev_dist = self.last_dist.get(snake_idx, current_dist)
            if current_dist < prev_dist:
                reward += 1.0  # 接近食物奖励
            elif current_dist > prev_dist:
                reward -= 1.0  # 远离惩罚
            reward += 0.5 / (current_dist + 1)
            self.last_dist[snake_idx] = current_dist
        if self.steps[snake_idx] >= self.grid_size * 2:
            reward -= 5.0  # 超时惩罚
        return reward
    
         def step(self, actions):
          ..... //省略部分代码
        # 检查碰撞(不允许进入其他蛇的身体)
            if self._check_collision(i, new_head):
                self.dones[i] = True
                rewards[i] -= 40  # 撞墙或撞到其它蛇的惩罚
                continue
    
            # 移动蛇身:将新头插入,并在未吃食物时移除尾部
            self.snakes[i].insert(0, new_head)
            if new_head in self.foods:
                rewards[i] += 20  # 吃到食物奖励
                self.foods.remove(new_head)
                self.foods.append(self._spawn_food())
                self.steps[i] = 0  # 重置步数
            else:
                self.snakes[i].pop()
    
            # 动态奖励:根据与食物的距离变化、步数等
            rewards[i] += self._calculate_reward(i)
      ```
    但实际训下来,参数迁移后,效果很差,认为是训练策略的问题,但用时已经很久了,当一次体验尝试
    
    
    
→ 📖 Q3.4(P) 请说明针对该任务,你们采取了哪些策略来优化决策。具体而言,怎么避免死亡?怎么吃到更多果子?如何编程实现。

采用贪心的策略,选择相对距离最近的果子来吃,防止产生碰撞。
变成实现:

  • 障碍物设置
    将蛇当前的前三节(排除蛇尾)当作障碍物,则下一时刻如果相撞,也是同归于尽,我们认为我们的方法吃的较多,这样在1v1中是可行的,在4v4中相对会弱一些,代码实现如下,即将蛇身当作障碍物带入第二问的函数即可:
    // 根据所有蛇构造障碍物:对指定 snakeIndex,其它蛇的前三节(索引 0~5),蛇尾不必计算
    export function getObstaclesForSnake(snakeIndex: i32, allSnakes: Array<Array<i32>>): Array<i32> {
      let obstacles = new Array<i32>();
      for (let j = 0; j < allSnakes.length; j++) {
        if (j == snakeIndex) continue;
        let s = allSnakes[j];
        for (let k = 0; k < 6; k++) {
          obstacles.push(s[k]);
      }
      // obstacles.push(s[0] + 1);
      // obstacles.push(s[1]);
      // obstacles.push(s[0] - 1);
      // obstacles.push(s[1]);
      // obstacles.push(s[0]);
      // obstacles.push(s[1] + 1);
      // obstacles.push(s[0]);
      // obstacles.push(s[1] - 1);
    
    
      }
      return obstacles;
    }
    

软件度量

→ 📖 Q3.5(P) 请说明你们如何量度所实现的程序模块的有效性,例如:“如何说明我们的程序模块对弈能力很强?”尝试提出一些可能的定量分析方式。
  • 存活率:与不同的蛇进行对战,计算存活的比率
  • 得分:既然有直观的得分,其可以作为一个很好的标准
    我们的程序在博弈能力上稍显欠缺,主要的博弈点在于对对面运动的判断,以及相同最近距离果子的抢夺。

总结

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

2025.4.2 11:20

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

这次任务中,我结合传统优化算法和强化学习来优化多智能体贪吃蛇环境。传统算法通过基于距离的策略有效避免了蛇之间的冲突,并成功实现了果子的选择和避免死亡。然而,强化学习在多智能体系统中的应用效果有限,主要因为奖励函数和目标平衡问题。尽管如此,还是让我尝试了一次新的挑战。

结对项目总结

结对过程回顾和反思

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

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

时间控制有待改进,沟通问题以及协作默契需要提高。

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

优点
1.做事认真仔细,肯花时间
2.思维活跃,想法很好,敢于尝试创新
3.适应能力强,对于新的技术栈学习较快,擅长运用工具

缺点
代码能力较差,debug能力仍有欠缺

对结对编程的理解

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

优点:

  1. 可以提高代码质量:通过两人协作,代码更易于检测和修正。观察员可以在编写时发现潜在的错误,确保代码更符合需求。

  2. 知识共享以及领域扩展:结对编程促进了技能的交流。同时可以产生思维上的碰撞,能感受不同人的想法

  3. 增强沟通协作能力。

缺点:

  1. 有可能会产生1+1<1的效果,对于两个人代码习惯以及思维不同时,可能会降低效率;

  2. 持续的互动需要高度集中精力,可能导致长时间编程后产生疲劳感降低了并行开发能力;

  3. 对于沟通与团队协作的能力有一定要求,虽然可以锻炼,但是当严重不足时,会产生很大的负面效果。

结对编程是一种强调团队合作与即时反馈的工作方式,它可以在提高代码质量的同时促进开发者之间的知识共享与技能提升。但我感觉也不是适合所有人,还是要适当选择适合自己的编程形式

代码实现提交

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

https://github.com/gaoxiaomo/BUAASE2025-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 @ 2025-04-06 19:58  筱陌、清风  阅读(19)  评论(0)    收藏  举报