CS61A-Spring2022

Project 3: Ants Vs. SomeBees

蜜蜂来了!用蚂蚁继承者们创造更好的士兵。

说明

重要的提交说明: 为了获得全部分数,

尽量按顺序尝试这些问题,因为一些后面的问题在实施时将涉及到前面的问题,因此在运行 ok 测试时也要注意。

整个项目可以和一个伙伴一起完成。

3 月 9 日(星期三) 之前提交整个项目,你可以得到 1 分奖励。

在这个项目中,你将创建一个名为蚂蚁大战蜜蜂的 塔防 游戏。作为蚁后,你要用你能想到的最勇敢的蚂蚁来补充你的蚁群。你的蚂蚁必须保护它们的蚁后不被入侵你领土的邪恶蜜蜂的伤害。通过向蜜蜂投掷树叶来伤害它们,它们就会被消灭。如果不能充分地纠缠空中的入侵者,你的蚁后就会屈服于蜜蜂。这个游戏的灵感来自于 PopCap Games 的 《植物大战僵尸》

这个项目使用面向对象的编程范式,重点是《Composing Programs》 第 2.5 章 的材料。该项目还涉及理解、扩展和测试一个大型程序。

过去,当学生在没有彻底阅读问题描述的情况下试图实现这些功能时,他们经常会遇到问题。😱 在开始编码之前,彻底阅读每个描述。

下载起始文件

ants.zip 压缩包包含了几个文件,但你所有的修改都将在 ants.py 中进行。

组织工作

该项目总分 24 分。 22 分是正确性, 1 分是在第一个检查点日期前提交第一阶段, 1 分是在第二个检查点日期前提交第二阶段。

此外,还有一些加分的机会。在 3 月 9 日(星期三) 之前提交整个项目,可以得到 1 分,而提交额外的问题,可以得到 2 分。

重要提示: 为了获得蚂蚁的所有可能的分数,你的整个项目的实施,包括额外的问题,必须在截止日期前提交。

你将提交以下文件:

你不需要修改或上交任何其他文件来完成这个项目。要提交该项目,请运行以下命令:

python3 ok --submit

你能够在 Ok 面板 上查看你的提交。

对于我们要求你完成的功能,可能有一些我们提供的初始代码。如果你不愿意使用这些代码,可以随时删除它,然后从头开始。你也可以在你认为合适的时候添加新的功能定义。

然而,请不要修改任何其他功能或编辑任何未列出的文件。 这样做可能会导致你的代码无法通过我们的自动评分测试。此外,请不要改变任何函数的签名(名称、参数顺序或参数数量)。

在整个项目中,你应该测试你代码的正确性。经常测试是很好的做法,这样就可以很容易地分辨出出现的任何问题。然而,你不应该测试得太频繁,以使自己有时间思考问题。

我们提供了一个名为 ok自动评分器 ,以帮助你测试你的代码并跟踪你的进度。在你第一次运行自动评分器时,你会被要求 用你的网络浏览器登录你的 Ok 账户 。请这样做。每次你运行 ok 时,它都会在我们的服务器上备份你的工作和进度。

ok 的主要目的是为了测试你的实现。

我们建议您 在完成每个问题后 提交。只有你的最后一次提交才会被打分。在你遇到提交问题时,多备份你的代码对我们来说也很有用。 如果你忘记提交,你的最后一份备份将自动转换为提交。

如果你不希望我们记录你的工作备份或你的进度信息,你可以运行

python3 ok --local

有了这个选项,任何信息都不会被发送到我们的课程服务器。如果你想以交互方式测试你的代码,你可以运行

python3 ok -q [question number] -i

并插入适当的问题编号(如 01 )。这将运行该问题的测试,直到你失败的第一个测试,然后给你一个机会来测试你写的交互式的函数。

你也可以在 OK 中使用调试打印功能,写上

print("DEBUG:", x) 

这将在你的终端产生一个输出,而不会导致 OK 测试因额外输出而失败。

游戏

蚂蚁与蜜蜂的游戏由一系列的回合组成。在每个回合中,新的蜜蜂可以进入蚁群。然后,新的蚂蚁被放置在那里以保卫他们的蚁群。最后,所有昆虫(先是蚂蚁,后是蜜蜂)采取个别行动。蜜蜂要么试图向隧道的尽头移动,要么刺伤挡路的蚂蚁。蚂蚁根据自己的类型采取不同的行动,如收集更多的食物或向蜜蜂投掷树叶。当一只蜜蜂到达隧道的尽头(你输了),蜜蜂摧毁了 QueenAnt (如果它存在的话)(你输了),或者整个蜜蜂舰队被征服(你赢了),游戏就结束了。

核心概念

The Colony. 这是游戏发生的地方。蚁群由几个 Place 组成,这些地方被连在一起,形成一条隧道,蜜蜂可以通过。蜂群也有一定数量的食物,可以通过消耗这些食物来将蚂蚁放入隧道中。

Places. 一个地方链接到另一个地方,形成一条隧道。玩家可以将一只蚂蚁放入每个地方。然而,在一个地方可以有许多蜜蜂。

The Hive. 这是蜜蜂起源的地方。蜜蜂离开蜂巢进入蚁群。

Ants. 玩家从屏幕上方的可用蚂蚁类型中选择,将蚂蚁放入蚁群。每种类型的蚂蚁采取不同的行动,需要不同数量的蚁群食物来放置。两种最基本的蚂蚁类型是收割者( HarvesterAnt )和投掷者( ThrowerAnt ),前者在每一回合为蚁群增加一种食物,后者在每一回合向蜜蜂投掷一片树叶。你将会创造更多类型的蚂蚁。

Bees. 在这个游戏中,蜜蜂是玩家必须保卫蚁群的敌对力量。每一回合,如果没有蚂蚁挡路,蜜蜂要么前进到隧道中的下一个地方,要么刺死挡路的蚂蚁。当至少有一只蜜蜂到达隧道的尽头时,蜜蜂就赢了。

核心类

上面描述的概念都有一个相应的类,它封装了该概念的逻辑。下面是这个游戏中涉及的主要类的摘要:

游戏布局

下面是一个游戏状态的可视化图。当你通过解锁测试和问题时,我们建议画出类似的图来帮助你理解。

类图

为了帮助直观地了解所有的类是如何组合在一起的,我们还创建了一个类图,供你在工作时参考,你可以在 这里 找到:

开始游戏

游戏可以在两种模式下运行:作为一个基于文本的游戏或使用图形用户界面(GUI)。在这两种情况下,游戏逻辑都是一样的,但 GUI 执行了一个回合时间限制,使玩游戏更加刺激。基于文本的界面是为调试和开发提供的。

文件是根据这两种模式分开的。 ants.py 对图形或回合时间限制一无所知。

要开始一个基于文本的游戏,运行

python3 ants_text.py

要启动一个图形游戏,运行

python3 gui.py

当你启动图形版本时,应该出现一个新的浏览器窗口。在初始实现中,你有无限的食物,你的蚂蚁只能在它们当前的 Place 向蜜蜂扔树叶。在你完成问题 2 之前,GUI 可能会崩溃,因为它对“地点”还没有一个完整的概念。不管怎样,请试着玩这个游戏吧!你需要放置大量的 ThrowerAnt (第二种类型),以防止蜜蜂接近你的蜂后。

这个游戏有几个选项,你将在整个项目中使用,你可以用 python3 ants_text.py --help 查看。

usage: ants_text.py [-h] [-d DIFFICULTY] [-w] [--food FOOD]

Play Ants vs. SomeBees

optional arguments:
  -h, --help     show this help message and exit
  -d DIFFICULTY  sets difficulty of game (test/easy/normal/hard/extra-hard)
  -w, --water    loads a full layout with water
  --food FOOD    number of food to start with when testing

入门视频

这些视频可以为解决项目中的编码问题提供一些有用的指导。

要看这些视频,你应该登录到你的 berkeley 邮箱。

YouTube link

Phase 1: Basic gameplay

重要的提交说明: 为了获得全部分数,请在 3 月 3 日星期四 之前提交第一阶段的内容(获得 1 分)。

在第一阶段,你将完成部分内容,以实现两种基本 Ant 的基本游戏: HarvesterAntThrowerAnt

Problem 0 (0 pt)

在你阅读了 整个 ants.py 文件之后,请回答下列问题。

要提交你的答案,请运行:

python3 ok -q 00 -u

如果你在回答这些问题时被卡住了,你可以试着再读一遍 ants.py ,参考上面的 核心概念 / 的部分,或者在 Piazza 上的问题 0 主题中提问。

  1. 昆虫的 health 属性的意义是什么?这个值会改变吗?如果是的话,如何改变?
  2. 以下哪项是 Insect 类的类属性?
  3. Ant 类的 health 属性是一个实例属性还是一个类属性?为什么?
  4. Ant 子类(如 ThrowerAnt )的 damage 属性是实例属性还是类属性?为什么?
  5. AntBee 都继承自哪个类?
  6. Ant 的实例和 Bee 的实例有什么共同点?
  7. 在任何时候,一个 Place 可以有多少只昆虫(在问题 8 之前)?
  8. Bee 在一个回合中会做什么?
  9. 游戏什么时候会输?

记住要执行:

python3 ok -q 00 -u

关于解锁测试的说明: 如果你想在完成解锁测试后复习解锁问题,你可以导航到( ants 文件夹内), tests 文件夹。例如,在解锁问题 0 之后,你可以在 tests/00.py 查看解锁测试。

Problem 1 (1 pt)

A 部分: 目前,放置任何类型的 Ant 都没有成本,因此对游戏没有挑战。基础类 Antfood_cost 为零。根据下表中的“食物成本”一栏,为 HarvesterAntThrowerAnt 重写这个类属性。

食物成本 最初的健康值
`HarvesterAnt` 2 1
`ThrowerAnt` 3 1

B部分: 现在放置 Ant 需要花费食物,我们需要能够收集更多的食物!为了解决这个问题,我们需要实现 HarvesterAnt 类。 HarvesterAntAnt 的一种类型,它的 action 是为 gamestate.food 总数增加一。

在编写任何代码之前,先解锁测试,以验证你对问题的理解:

python3 ok -q 01 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你代码的正确性:

python3 ok -q 01

试着通过运行 python3 gui.py 来玩这个游戏。一旦你放置了一个 HarvesterAnt ,你应该每回合都能积累食物。你也可以放置 ThrowerAnts ,但你会发现它们只能攻击在自己 Place 上的蜜蜂,这让你有点难赢。

Problem 2 (1 pt)

在这个问题中,你将通过添加跟踪入口的代码来完成 Place.__init__ 。现在,一个 Place 只跟踪它的 exit 。我们希望一个 Place 也能跟踪它的入口。一个 Place 只需要跟踪一个 entrance 。当一只 Ant 需要查看它在隧道中的前面有哪些 Bee 时,跟踪入口将是非常有用的。

然而,简单地将一个入口传递给一个 Place 构造函数将是有问题的;在创建一个 Place 之前,我们需要同时拥有出口和入口(这是一个 鸡或蛋 的问题。)为了解决这个问题,我们将用以下方式来跟踪入口。 Place.__init__ 应该使用这个逻辑:

提示: 记住,当调用 __init__ 方法时,第一个参数, self ,被绑定到新创建的对象。

提示: 如果事情变得混乱,试着画出两个相邻的 Place 。在图形用户界面中,一个网格的 entrance 在其右边,而 exit 在其左边。

提示: 记住, Places 不是存储在一个列表中,所以你不能通过索引来访问它们。这意味着你 不能 做像 colony[index + 1] 这样的事情来访问一个相邻的 Place 。你怎么能从一个网格移动到另一个网格呢?

在编写任何代码之前,解开测试,以验证你对问题的理解:

python3 ok -q 02 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你代码的正确性:

python3 ok -q 02

Problem 3 (2 pt)

为了让 ThrowerAnt 扔出一片叶子,它必须知道要打哪只蜜蜂。 ThrowerAnt 类中提供的 nearest_bee 方法的实现只允许它们击中同一 Place 的蜜蜂。你的工作是修复它,使 ThrowerAnt 能够 throw_at 它前面最近的、 不在 Hive 中的蜜蜂。这包括与 ThrowerAnt 在同一 Place 的蜜蜂。

提示: 所有 Place 都有一个 is_hive 属性,当该地点是 Hive 时,该属性为 True

改变 nearest_bee ,使其从最近的地方返回一只随机的 Bee 。你的实现应该遵循这个逻辑:

提示: ants.py 中提供的 random_bee 函数从蜜蜂列表中返回一只随机蜜蜂,如果列表为空,则返回 None

提示: 作为提醒,如果一个网格 Place 没有蜜蜂,那么该网格 Place 实例的 bees 属性将是一个空列表。

提示: 对测试案例的可视化有困难吗?试着把它们画在纸上吧! 游戏布局 中提供的示例图显示了这个问题的第一个测试用例。

在编写任何代码之前,解开测试以验证你对问题的理解:

python3 ok -q 03 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 03

在实现了 nearest_bee 之后, ThrowerAnt 应该能够 throw_at 到它面前的、不在 Hive 中的 Bee 。请确保你的蚂蚁做正确的事情 开始一个有 10 个食物的游戏(为了方便测试):

python3 gui.py --food 10

请确保在截止日期前使用以下命令提交:

python3 ok --submit

你可以通过运行来检查以确保你已经完成了第一阶段的问题:

python3 ok --score

恭喜你!你已经完成了这个项目的第一阶段!

Phase 2: Ants!

重要的提交说明: 为了获得全部分数,

现在你已经实现了两种 Ant 的基本玩法,让我们为蚂蚁攻击蜜蜂的方式添加一些趣味。在这个阶段,你将实现几种不同的 Ant ,并采取不同的攻击策略。

在本节中你实现了每个 Ant 子类后,你需要将其 implemented 的类属性设置为 True ,这样该类型的蚂蚁就会在 GUI 中显示出来。请自由地用每只新的蚂蚁试玩,以测试其功能!

用你第二阶段创建的蚂蚁,尝试用 python3 gui.py -d easy 来对抗多通道布局中的全群蜜蜂,如果你想要一个真正的挑战,可以尝试 -d normal-d hard、 或 -d extra-hard !如果蜜蜂太多,无法消灭,你可能需要创建一些新的蚂蚁。

Problem 4 (2 pt)

ThrowerAnt 对蜜蜂来说是一个强大的威胁,但它的食物成本很高。在这个问题中,你要实现两个 ThrowerAnt 的子类,它们的成本较低,但对它们的投掷距离有限制。

这两种专门的投掷者都不能 throw_at 正好在 4 Place 之外的蜜蜂。

食物成本 最初的健康值
`ShortThrower` 2 1
`LongThrower` 2 1

为了实现这些新的投掷蚂蚁,你的 ShortThrowerLongThrower 类应该从基础 ThrowerAnt 类中继承 nearest_bee 方法。选择投掷蚁攻击哪只蜜蜂的逻辑是相同的,只是 ShortThrowerLongThrower 分别有一个最大和最小范围。

要做到这一点,修改 nearest_bee 方法以引用 min_rangemax_range 属性,并且只在范围内返回一只蜜蜂。

确保在 ThrowerAnt 类中给这些 min_rangemax_range 属性适当的值,这样 ThrowerAnt 的行为就不会改变。然后,用适当的约束范围实现子类 LongThrowerShortThrower

你应该 需要在 ThrowerAntShortThrowerLongThrower 之间重复任何代码。

提示: float('inf') 返回一个无限的正值,用 float 表示,可以与其他数字比较。

提示: 你可以在 Python 中使用连锁不等式:例如, 2 < x < 6 将检查 x 是否在 2 和 6 之间。另外, min_rangemax_range 应该标记一个包容性的范围。

重要提示: 确保你的类属性被称为 max_rangemin_range 测试直接引用这些属性名称,如果你为这些属性使用其他名称,将会出错。

不要忘记将 LongThrowerShortThrowerimplemented 类属性设置为 True

在编写任何代码之前,请解开测试,以验证你对问题的理解:

python3 ok -q 04 -u

写完代码后,测试你的实现(重新运行 03 的测试,以确保它们仍然有效):

python3 ok -q 03
python3 ok -q 04

👩🏽‍💻👨🏿‍💻 结对编程? 记住要交替扮演司机和导航员的角色。驾驶员控制键盘;导航员观察,提出问题,并提出想法。

Problem 5 (3 pt)

实现 FireAnt ,当它受到对方伤害时,它就会给对方造成伤害。具体来说,如果它受到了一定 amount 的健康值的伤害,它就会对其所在的所有蜜蜂造成一定 amount 的伤害(这被称为 反射伤害 )。如果它死了,它会造成额外的伤害,这由它的 damage 属性指定,如 FireAnt 类中定义的,其默认值为 3

要实现这一点,请重写 Antreduce_health 方法。你覆盖的方法应该调用从超类( Ant )继承的 reduce_health 方法来减少当前 FireAnt 实例的健康值。在 FireAnt 实例上调用 继承reduce_health 方法可以减少昆虫的 health 状况,如果它的 health 状况达到零或更低,就会将昆虫从其位置上移除。

提示: 不要 调用 self.reduce_health ,否则你会被困在一个递归循环中。(你能明白为什么吗?)

然而,你的方法还需要包括反射性伤害逻辑:

重要提示: 记住,当任何一只 Ant 失去所有的健康值时,它就会被从它的网格 place 移走,所以要仔细注意你在 reduce_health 中的逻辑顺序。

食物消耗 初始的健康值
`FireAnt` 5 3

提示: 伤害一只蜜蜂可能会导致它被移出它的位置。如果你在一个列表上迭代,但同时改变了该列表的内容,你 可能不会访问到所有的元素 。这可以通过对列表进行复制来防止。你可以使用一个 list 切片,或者使用内置的 list 函数。

>>> lst = [1,2,3,4]
>>> lst[:]
[1, 2, 3, 4]
>>> list(lst)
[1, 2, 3, 4]
>>> lst[:] is not lst and list(lst) is not lst
True

一旦你完成了 FireAnt 的实现,给它一个 implemented 的类属性,值为 True

注意: 即使你覆盖了超类的 reduce_health 函数( Ant.reduce_health ),你仍然可以在你的实现中通过调用这个方法来使用它。注意这不是递归。(为什么不呢?)

在写任何代码之前,先解锁测试以验证你对问题的理解:

python3 ok -q 05 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 05

你也可以通过玩一两次游戏来测试你的程序。一只 FireAnt 在被蛰时应该会消灭所有同处的蜜蜂。开始一个有十个食物的游戏(为了方便测试):

python3 gui.py --food 10

Problem 6 (1 pt)

我们将通过实施墙蚁( WallAnt )来为我们光荣的大本营增加一些保护, WallAnt 是一种每回合不做任何事情的蚂蚁。墙蚁很有用,因为它有一个很大的 health 值。

食物消耗 初始的健康值
`WallAnt` 4 4

与之前的蚂蚁不同,我们没有为你提供类头。从头开始实现 WallAnt 类。给它一个值为 'Wall' 的类属性 name (这样图形界面才能正常工作)和一个值为 True 的类属性 implemented (这样你就能在游戏中使用它)。

提示: 开始时,看看前面问题中的蚂蚁是如何实现的!

在写任何代码之前,先解锁测试以验证你对问题的理解:

python3 ok -q 06 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 06

Problem 7 (3 pt)

实现 HungryAnt ,它将从自己的网格 place 上随机选择一只 Bee ,并通过吃掉整只 Bee 来对其造成伤害。在吃掉一只蜜蜂后, HungryAnt 必须花 3 个回合的时间咀嚼才能再次进食。当 HungryAnt 在咀嚼时,它不能吃任何 Bee (对其造成伤害)。3 个回合后,如果没有蜜蜂可以吃, HungryAnt 就不会做任何事情。

我们没有为你提供一个类头。从头开始实现 HungryAnt 类。给它一个 'Hungry' 的类属性 name (以便图形界面工作)和一个值为 True 的类属性 implemented (以便你可以在游戏中使用它)。

提示: 当一只 Bee 被吃掉时,它应该失去所有的健康值。有没有一个现有的函数,我们可以在 Bee 上调用,使其健康值降至 0 ?

食物消耗 初始的健康值
`HungryAnt` 4 1

HungryAnt 一个 time_to_chew 属性,用来存储 HungryAnt 需要咀嚼的回合数(设置为 3 )。同时,给每个 HungryAnt 一个 instance 属性 chew_timer ,用来计算它剩余的咀嚼回合数(初始化为 0 ,因为它一开始就没吃东西。你也可以把 chew_timer 看作是 HungryAnt 可以吃掉另一只 Bee 之前的回合数)。

实现 HungryAntaction 方法。首先,检查它是否在咀嚼;如果是,则递减其 chew_timer 。否则,随机吃掉一只 Bee ,将该 Bee 的健康值降低到 0 。

提示: 除了 action 方法之外,确保你也实现了 __init__ 方法,这样 HungryAnt 在开始时就有了适当的 health

在编写任何代码之前,请解锁测试以验证你对问题的理解:

python3 ok -q 07 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 07

我们现在有一些伟大的进攻部队来帮助征服蜜蜂,但我们也要保持我们的防御工作。在这个阶段,你将实施具有特殊防御能力的蚂蚁,如增加健康值和保护其他蚂蚁的能力。

👩🏽‍💻👨🏿‍💻 结对编程? 这将是一个转换角色的好时机。交换角色可以确保你们都能从在每个角色中的学习经历中受益。

Problem 8 (3 pt)

现在,我们的蚂蚁是相当虚弱的。我们想提供一种方法,帮助它们在蜜蜂的攻击下坚持更长时间。那就要用 BodyguardAnt

食物消耗 初始的健康值
`BodyguardAnt` 4 2

BodyguardAnt 与普通蚂蚁不同,因为它是一个 ContainerAnt ;它可以在一个网格 Place 容纳另一只蚂蚁并保护它。当一只 Bee 蜇了在一个在网格 Place 的蚂蚁时,只有容器被损坏。容器中的蚂蚁仍然可以执行它原来的动作。如果容器灭亡了,被包含的蚂蚁仍然留在这个地方(然后可能会被伤害)。

每个 ContainerAnt 都有一个实例属性 ant_contained ,用来存储它所包含的蚂蚁。这个蚂蚁, ant_contained ,一开始是 None ,表示还没有蚂蚁被存储。实现 store_ant 方法,使其将 ContainerAntant_contained 实例属性设置为传入的 ant 参数。同时实现 ContainerAntaction 方法,以便在当前包含蚂蚁时执行其 ant_contained 的动作。

此外,你还需要在整个程序中进行以下修改,以便一个容器和它所包含的蚂蚁可以同时占据一个地方(每个地方最多有两只蚂蚁),但前提是正好有一只是容器:

  1. 有一个 Ant.can_contain 方法,但它总是返回 False 。用 ContainerAnt.can_contain 覆盖方法,使其接受另一个蚂蚁 other 作为参数,并在以下情况下返回 True
    • 这个 ContainerAnt 并没有包含另一只蚂蚁。
    • 另一只蚂蚁不是一个容器。
  2. 修改 Ant.add_to ,允许一只容器蚂蚁和一只非容器蚂蚁按照以下规则占据同一个位置:
    • 如果原本占据一个网格的蚂蚁可以包含被添加的蚂蚁,那么这两只蚂蚁都占据了这个网格,并且原本的蚂蚁包含了被添加的蚂蚁。
    • 如果被添加的蚂蚁可以包含原来在该网格的蚂蚁,那么两只蚂蚁都占据该网格,被添加的(容器)蚂蚁包含原来的蚂蚁。
    • 如果两只 Ant 都不能包含另一只,则引发与之前相同的 AssertionError (启动代码中已经存在的那个)。
    • 重要提示: 如果在一个特定的网格 Place 有两只 ant ,那么这个网格 Place 实例的蚂蚁属性应该指的是容器蚂蚁,而容器蚂蚁应该包含非容器蚂蚁。
  3. 添加一个 BodyguardAnt.__init__ ,设置蚂蚁的初始健康值。

提示: 你可能会发现每个 Ant 都有的 is_container 属性对于检查特定的 Ant 是否是一个容器很有用。你也应该利用你写的 can_contain 方法,避免重复的代码。

ContainerAnt.__init__ 的构造函数实现如下:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.ant_contained = None

正如我们在 Hog 中看到的, args 被绑定到所有的位置参数(也就是所有不带关键字的参数),而 kwargs 被绑定到所有的关键字参数。这确保了两组参数都被传递给 Ant 构造函数。

实际上,这意味着构造函数与它的父类的构造函数( Ant.__init__ )完全相同,但在这里我们还设置了 self.ant_contained = None

一旦你完成了 BodyguardAnt 的实现,给它一个实现了的类属性 implemented ,值为 True

注意: 如果你通过 VSCode Pylance 扩展得到 Ant.add_to 的“unreachable code”警告,那么忽略这个特定的警告就可以了,因为代码实际上已经运行了(这种情况下的警告是不准确的)。

在编写任何代码之前,请解锁测试以验证你对问题的理解:

python3 ok -q 08 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 08

Problem 9 (1 pt)

BodyguardAnt 提供了很好的防御,但他们说最好的防御是一个对他们进攻。 TankAnt 是一个容器,可以保护它所在网格的蚂蚁,同时每回合对它所在的所有蜜蜂造成 1 点伤害。

食物消耗 初始的健康值
`TankAnt` 6 2

我们没有为你提供一个类头。从头开始实现 TankAnt 类。给它一个值为 'Tank' 的类属性 name (以便图形界面工作)和一个值为 True 的类属性 implemented (以便你可以在游戏中使用它)。

你应该不需要修改 TankAnt 类之外的任何代码。如果你发现自己需要在其他地方进行修改,请寻找一种方法来编写上一个问题的代码,使其不仅适用于 BodyguardAntTankAnt 对象,而且适用于一般的容器蚂蚁。

提示: 你需要在 TankAnt 中覆盖父类的方法是 __init__action

提示: 就像 FireAnt 一样,伤害一只蜜蜂有可能会导致它被移走。

在编写任何代码之前,请解开测试以验证你对问题的理解:

python3 ok -q 09 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 09

Phase 3: Water and Might

重要的提交说明: 为了获得全部分数,

3 月 9 日(星期三) 之前提交整个项目,你将得到额外的一分。

在最后阶段,你要为游戏增加最后一击,引入一种新的网格和能够占据这个网格的新蚂蚁。这些蚂蚁中,有一只是最重要的蚂蚁:蚁王!

Problem 10 (1 pt)

让我们给他们加水吧!目前,只有两种类型的场所,即 Hive 和基本 Place 。为了使游戏更加有趣,我们将创建一种新的 Place ,叫做 Water

只有防水的昆虫才能被放在 Water 里。为了确定一只 Insect 是否防水,给 Insect 类添加一个新的类属性,名为 is_waterproof ,设置为 False 。由于蜜蜂可以飞行,所以将它们的 is_waterproof 属性设置为 True ,覆盖继承的值。

现在,实现 Wateradd_insect 方法。首先,无论昆虫是否防水,都将其添加到该处。然后,如果该昆虫不防水,将该昆虫的健康值降为 0 。相反,使用已经定义好的方法。

在编写任何代码之前,先解锁测试,以验证你对问题的理解:

python3 ok -q 10 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 10

一旦你完成了这个问题,玩一个包括水的游戏。要访问包括水的 wet_layout ,请在启动游戏时添加 --water 选项(或简称 -w )。

python3 gui.py --water

👩🏽‍💻👨🏿‍💻 结对编程? 记住要交替扮演司机和导航员的角色。驾驶员控制键盘;导航员观察,提出问题,并提出想法。

Problem 11 (1 pt)

目前还没有可以放在 Water 上的蚂蚁。实现 ScubaThrower ,它是 ThrowerAnt 的一个子类,成本更高、更防水,但其他方面与基类相同。当 ScubaThrower 被放置在 Water 中时,它不应该降低健康值。

食物消耗 初始的健康值
`ScubaThrower` 6 1

我们没有为你提供一个类头。从头开始实现 ScubaThrower 类。给它一个值为 'Scuba' 的类属性 name (这样图形界面才能正常工作),并记得将类属性 implemented 设置为 True (这样你就能在游戏中使用它)。

在编写任何代码之前,先解锁测试,以验证你对问题的理解:

python3 ok -q 11 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 11

Problem 12 (3 pt)

最后,实现 QueenAnt 。蚁后是一个防水的 ScubaThrower ,通过她的勇敢来激励她的同伴。除了标准的 ScubaThrower 行动外, QueenAnt 每次执行行动时,她身后的所有蚂蚁的伤害都会加倍。一旦一只蚂蚁的伤害被加倍,它在接下来的回合中就不会再被加倍。

注意: FireAnt 的反射伤害不应该被加倍,只有当它的生命值减少到 0 时,它的额外伤害才会被加倍。

食物消耗 初始的健康值
`QueenAnt` 7 1

然而,巨大的权力伴随着巨大的责任。 QueenAnt 受三个特殊规则的制约:

  1. 如果蚁后的健康值降低到 0 ,那么蚂蚁就会输。你需要在 QueenAnt 中覆盖 Ant.reduce_health 并在这种情况下调用 ants_lose() ,以便向模拟器发出游戏结束的信号。(如果有蜜蜂到达隧道的尽头,蚂蚁也会输。)
  2. 蚁后只能有一个。第二个蚁后不能被创建。为了检查蚂蚁是否可以被创建,我们使用 Ant.construct() 类方法,如果可以创建的话,就创建一个蚂蚁,如果不能创建的话,就返回 None 。你需要把 Ant.construct 作为 QueenAnt 的一个类方法来覆盖,以便增加这个检查。为了跟踪一个蚁后是否已经被创建,你可以使用一个添加到当前 GameState 中的实例变量。
  3. 皇后不能被移除。试图移除蚁后应该没有效果(但不应该导致错误)。你需要在 QueenAnt 中覆盖 Ant.remove_from 来执行这个条件。

提示: 考虑一下如何调用 QueenAnt 超类的构造方法。记住,你最终要构造的是 QueenAnt ,而不是一只普通的 AntScubaThrower

提示: 你可以通过从蚂蚁的 place.exit 开始,然后重复地沿着它的 exit 找到 QueenAnt 后面隧道中的每个 Place 。在隧道尽头的 PlaceexitNone

提示: 为了避免一个蚂蚁的伤害被加倍,请将被加倍伤害的蚂蚁以一种在调用 QueenAnt.action 时持续存在的方式进行标记。

提示: 当加倍蚂蚁的伤害时,请记住一个 Place 可能有不止一只蚂蚁,例如一只蚂蚁正在保护另一只。

在编写任何代码之前,请解锁测试以验证你对问题的理解:

python3 ok -q 12 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q 12

Extra Credit (2 pt)

在办公时间和项目聚会期间,工作人员将优先帮助学生解决必要的问题。除非 队列 是空的,否则我们不会对这个问题提供帮助。

实现两个最后的投掷者蚂蚁,它们的伤害为零,而是在它们 throw_atBee 实例的 action 方法上应用一个临时的“状态”。这个“状态”持续一定的回合数,之后就不再生效了。

我们将实现两个继承自 ThrowerAnt 的新蚂蚁。

重要更新(3/7): 之前,规范中提到“如果蜜蜂被放慢速度,它们在受到惊吓时不会后退”。然而,实际的行为应该是:“如果蜜蜂被放慢速度且 gamestate.time奇数 ,则它们在受到惊吓时不会后退。”正如在测试行为中所描述和看到的那样(这些测试没有改变)。现在规格描述已经被更新,以反映这一行为。

对测试的评论进行了澄清: # 在这之后,不再被吓到,但仍然慢了 8 个回合 并且 # 这是一个偶数回合,所以它可以被吓到并向后移动 这指的是由于 gamestate.time 是偶数,蜜蜂现在可以由于被吓到(发生在 gamestate.time=0 )而进行第二次尝试后退。蜜蜂并 没有 第二次被吓到,它只是显示了从 gamestate.time=0 开始被吓到的效果。在蜜蜂根据测试评论进行了第二次尝试后,它不能再被吓到的状态所影响。

注意: 这一变化只影响到本页的规则,而不是问题本身的测试。由于测试仍然正确,因此这一变化不需要重新下载。

食物消耗 初始的健康值
`SlowThrower` 4 1
`ScaryThrower` 6 1

为了完成这两只蚂蚁的实现,你需要适当地设置它们的类属性,并实现 Bee 上的 slowscare 方法,将它们各自的状态应用于特定的蚂蚁。你可能还需要编辑 Bee 的其他一些方法。

在编写任何代码之前,请解锁测试,以验证你对问题的理解:

python3 ok -q EC -u

你可以运行一些提供的测试,但它们并不详尽:

python3 ok -q EC

请确保测试你的代码!你的代码应该能够在一个目标上应用多个状态;每个新的状态都适用于蜜蜂的当前(可能是以前受影响的)行动方法。

可选的问题

Optional Problem 1

在办公时间和项目聚会期间,工作人员将优先帮助学生解决必要的问题。除非 队列 是空的,否则我们不会对这个问题提供帮助。

实现 NinjaAnt ,它可以破坏所有经过的 Bee ,但永远不会被蜇伤。

食物消耗 初始的健康值
`NinjaAnt` 5 1

一个 NinjaAnt 不会阻挡飞过的 Bee 的路径。要实现这一行为,首先要修改 Ant 类,使其包括一个新的类属性 blocks_path ,并将其设置为 True ,然后在 NinjaAnt 类中将 blocks_path 的值覆盖为 False

其次,修改 Beeblocked 方法,如果 Bee 所在的 place 没有 Ant ,或者有 Ant 但其 blocks_path 属性为 False ,则返回 False 。现在 Bee 将直接飞过 NinjaAnt

最后,我们要让 NinjaAnt 对所有飞过的 Bee 造成伤害。在 NinjaAnt 中实现 action 方法,使所有与 NinjaAnt 在同一 placeBee 的健康值按其 damage 属性减少。与 FireAnt 类似,你必须对可能变化的蜜蜂列表进行迭代。

提示: 对测试案例的可视化有困难吗?试着把它们画在纸上吧!请看 游戏布局 中的例子以获得帮助。

在写任何代码之前,先解锁测试以验证你对问题的理解:

python3 ok -q optional1 -u

一旦你完成了解锁,就开始实施你的解决方案。你可以用以下方法检查你的正确性:

python3 ok -q optional1

作为一个挑战,请尝试只用 HarvesterAntNinjaAnt 来赢得比赛。

Optional Problem 2

在办公时间和项目聚会期间,工作人员将优先帮助学生解决必要的问题。除非 队列 是空的,否则我们不会对这个问题提供帮助。

我们已经秘密地开发了这种蚂蚁很长时间。它是如此的危险,以至于我们不得不把它锁在超级隐蔽的 CS 61A 地下保险库中,但我们最终认为它已经准备好在战场上使用了。在这个问题中,你将实现最终的蚂蚁—— LaserAnt ,一个有变化的 ThrowerAnt

食物消耗 初始的健康值
`LaserAnt` 10 1

LaserAnt 发射出强大的激光,破坏所有敢于站在它面前的人。不管是 Bee 还是 Ant ,都有可能被 LaserAnt 破坏。当一只 LaserAnt 采取行动时,它将伤害其所在的所有 Insect (不包括它自己,但包括它的容器(如果有的话))和它前面的 Place ,不包括 Hive

如果是这样的话, LaserAnt 就太强大了,我们无法遏制。 LaserAnt 的基础伤害为 2 。但是, LaserAnt 的激光有一些怪癖。激光每离开 LaserAnt 的位置就会被削弱 0.25 。此外, LaserAnt 的电池是有限的。每当 LaserAnt 实际伤害一只 Insect 时,其激光的总伤害就会减少 0.0625 (1/16)。如果 LaserAnt 的伤害因为这些限制而变成了负值,它就会简单地造成 0 伤害。

在一个回合内,事物被损坏的确切顺序是没有规定的。

为了完成这个终极蚂蚁的实现,请通读 LaserAnt 类,适当地设置类的属性,并实现以下两个函数:

  1. insects_in_front 是一个实例方法,由 action 方法调用,它返回一个字典,其中每个键是一只 Insect ,每个对应的值是该 InsectLaserAnt 的距离(以位数计)。这个字典应该包括所有在同一网格或在 LaserAnt 前面的 Insects ,不包括 LaserAnt 本身。
  2. calculate_damage 是一个实例方法,它接收 distance ,即昆虫离 LaserAnt 实例的距离。它返回 LaserAnt 实例应该施加的伤害,基于以下几点:
  3. InsectLaserAnt 实例的 distance
  4. 这个 LaserAnt 已经伤害的 Insects 的数量,存储在 insects_shot 实例属性中。

除了实现上述方法外,你可能需要根据需求修改、添加或使用 LaserAnt 类中的类或实例属性。

注意: 本题没有解锁测试。

你可以运行提供的测试,但它并不详尽:

python3 ok -q optional2

请确保测试你的代码!

项目提交

这时,运行整个自动评分器,看看是否有任何测试没有通过:

python3 ok

你也可以检查你在项目的每个部分的得分,包括加分问题:

python3 ok --score

满意之后,提交即可完成该项目。

python3 ok --submit

如果你有伙伴,请确保将他们加入到 okpy 上的项目提交中。

现在你已经完成了这个项目! 如果你还没有,你应该试着玩玩这个游戏!

python3 gui.py [-h] [-d DIFFICULTY] [-w] [--food FOOD]

鸣谢: Tom Magrino 和 Eric Tzeng 与 John DeNero 一起开发了这个项目。 Jessica Wan 提供了原始艺术作品。 Joy Jeng 和 Mark Miyashita 发明了蚁后。其他许多人也为这个项目做出了贡献!

新的概念作品是由 Alana Tran, Andrew Huang, Emilee Chen, Jessie Salas, Jingyi Li, Katherine Xu, Meena Vempaty, Michelle Chang 和 Ryan Davis 绘制的。