发布网友 发布时间:2024-09-26 07:00
共1个回答
热心网友 时间:2024-10-07 00:50
大家好,日拱一卒,我是梁唐。本文始发于公众号:Coder梁
今天我们继续来肝伯克利的CS61A,这一次我们做的是这门课的第二个大作业。
这个项目非常有意思,让我们做一个类似植物大战僵尸的小游戏。只不过这里改成了蚂蚁大战蜜蜂,蜜蜂一波一波来袭,我们要建造各种功能的蚂蚁抵御蜜蜂的进攻。
完全做好之后的运行效果是这样的:
课程链接
项目原始文档
Github
这一次的项目难度不算高,主要讲解的是关于Python当中面向对象的部分,教我们学会设计类,以及使用类的继承。
核心概念Colony:游戏发生的场景,由多个通路(tunnel)组合而成,每一个通道是由一个一个网格(place)组成的
Places:网格,每一个网格连接另外的网格组成通路,玩家可以在网格上放置一只蚂蚁,然而每个网格可以有多只蜜蜂
The hive:蜂巢,蜜蜂的老家,蜜蜂从hive中出来,来到colony中
Ants:蚂蚁,玩家在游戏当中用来操控对付蜜蜂的士兵。每一种蚂蚁在每一个回合可以采取一个特定的行动(action),生产蚂蚁需要消耗一定的食物(food)。最基础的两种蚂蚁是**HarvesterAnt**(收割蚂蚁),它每个回合可以采集1点食物,和**ThrowerAnt**(投掷蚂蚁),每个回合每个回合可以朝蜜蜂丢一片树叶
Bees:蜜蜂,游戏当中我们需要对付的敌人。每一个回合当中,如果没有蚂蚁拦路的话,蜜蜂会前进一步。否则会攻击拦路的蚂蚁,当有一只蜜蜂到达tunnel末端时游戏结束
Queen Ant:蚁后,每局游戏只有一只蚁后。蚁后也有攻击能力,除此之外它还能鼓舞士气,提升其他蚂蚁的攻击力。如果蜜蜂击杀了蚁后,同样游戏结束
运行游戏有两种运行游戏的方式,一种是基于文本命令行的形式,用于开发测试:
python3?ants.py我们也可以通过游戏界面运行游戏,用于最终演示:
python3?gui.py?-d?easy?--food?10-d 控制难度 test/easy/medium/hard/insane
-w 地图是否有水
--food 开局食物数量
Phase 1 gameplay在第一个阶段,实现基本的蚂蚁(HavesterAnt和ThrowerAnt)。
阶段结束时,可以运行最基础的游戏版本
Problem 0阅读代码回答一下几个问题:
insect类中的armor属性的作用是?在游戏当中它会改变吗?如果会改变,触发条件是?
Ant类所有的属性有哪些?
Ant类的armor属性是类属性还是实例属性?为什么
Ant部分子类的damage属性是类属性还是实例属性,为什么
Ant和Bee在是从哪一个类中继承来的?
Ant和Bee类的实例有什么共同点?
在任意给定时间,一个Place可以出现多少insect?
我们可以以命令行交互的形式回答这些问题(原问题是英文的),下载项目代码之后,输入一下命令即可:
python3?ok?-q?00?-u题目和选项都会以命令行文本的形式展示,通过输入选项的形式来答题。
老师为每道题都设置了测试样例,但是需要我们先通过问题才能使用这些样例。解锁测试样例和进行测试不会进行身份验证,即使我们不是伯克利的学生也能享用,非常非常人性化。
Problem 1首先添加食物的开销,以及开发HarvesterAnt。
目前生产蚂蚁不需要任何消耗,因此游戏完全没有难度。你会注意到Ant这个类中的属性food_cost设置成了0。在下列的子类当中覆盖这个属性,将它设置成正确的值。
ClassFood CostArmor HarvesterAnt21 ThrowerAnt31现在生产蚂蚁需要花费了,因此我们需要HarvesterAnt来收集食物。开发HarvesterAnt类,使得它每回合在行动时可以添加colony.food。
在开始开发之前,先回答问题进行测试,测试通过可以解锁测试样例:
python3?ok?-q?01?-u当完成实现之后,进行测试:
python3?ok?-q?01答案action函数会在每个回合被调用,被调用时将colony.food增加即可
class?HarvesterAnt(Ant):????"""HarvesterAnt?proces?1?additional?food?per?turn?for?the?colony."""????name?=?'Harvester'????implemented?=?True????food_cost?=?2????def?action(self,?colony):????????"""Proce?1?additional?food?for?the?COLONY.????????colony?--?The?AntColony,?used?to?access?game?state?information.????????"""????????#?BEGIN?Problem?1????????"***?YOUR?CODE?HERE?***"????????colony.food?+=?1????????#?END?Problem?1Problem 2完善Place类的构造函数,使得它能够追踪入口(entrance)。
目前,一个place仅仅会记录出口(exit),我们希望place也能记录它的入口。每一个place只会有一个入口,有了入口之后,Ant就可以知道面前有哪些蜜蜂了。
然而,只是简单地将一个entrance传进构造函数是有问题的。因为我们在当前place创建之前就需要这两个属性,会导致循环依赖。为了解决这个问题,我们采用如下方法:
新创建的Place的entrance为None
如果Place拥有一个exit,那么将exit的入口设置成Place
提示:
在__init__函数中,self会绑定当前对象
如果觉得困惑,可以先画出两个Place。在GUI当中,一个Place的入口是它的右边的Place,出口是它的左边
在开始编码之前,先回答问题,确保已经理解了
python3?ok?-q?02?-u完成之后,使用下列命令来测试:
python3?ok?-q?02答案exit是外界传入的,当exit不为空时,将exit.entrance设置成self即当前Place即可。
class?Place(object):????"""A?Place?holds?insects?and?has?an?exit?to?another?Place."""????def?__init__(self,?name,?exit=None):????????"""Create?a?Place?with?the?given?NAME?and?EXIT.????????name?--?A?string;?the?name?of?this?Place.????????exit?--?The?Place?reached?by?exiting?this?Place?(may?be?None).????????"""????????self.name?=?name????????self.exit?=?exit????????self.bees?=?[]????????#?A?list?of?Bees????????self.ant?=?None???????#?An?Ant????????self.entrance?=?None??#?A?Place????????#?Phase?1:?Add?an?entrance?to?the?exit????????#?BEGIN?Problem?2????????"***?YOUR?CODE?HERE?***"????????if?self.exit?is?not?None:????????????self.exit.entrance?=?self????????#?END?Problem?2Problem 3下面我们实现ThrowerAnt类,用来攻击蜜蜂。
首先,它必须知道它需要攻击哪一只蜜蜂。最初版本的代码nearest_bee方法,只会攻击和它在同一个格子里的蜜蜂。你的任务是修改它,让ThrowerAnt会对距离它最近的蜜蜂(蜂巢中的除外)使用throw_at方法。
nearest_bee方法随机返回距离它最近的格子当中的一只蜜蜂:
从当前ThrowerAnt在的Place开始遍历
对于每一个Place,如果它上面有蜜蜂, 返回任意一只。如果没有,检查它前一个Place(entrance)
如果没有蜜蜂可以攻击,返回None
提示:
random_or_none`函数可以随机返回序列中一个元素,如果序列为空时返回`None开始开发之前先回答问题,确保已经充分理解
python3?gui.py?-d?easy?--food?100开发完成之后,进行测试:
python3?gui.py?-d?easy?--food?101测试通过之后,可以模拟一下效果:
python3?gui.py?-d?easy?--food?102效果如下:
答案通过while循环来遍历place,直到place上有蜜蜂,随机返回一只即可。
python3?gui.py?-d?easy?--food?103Phase 2 Ants Attack现在你已经开发好了两个最基本的蚂蚁类型,可以进行最基础的游戏了。
在这个阶段当中,你将会开发更多的拥有不同技能的蚂蚁类型。当你完成了Ant子类的开发之后,你需要将其中implemented属性改成True才能在GUI中使用它。你可以在每完成一种新的蚂蚁之后进行游戏测试。
这个阶段开发完成之后,你可以尝试使用命令:python3 gui.py -d easy当前的蚂蚁类型来对决一大波蜜蜂。你也可以使用-d normal, -d hard或者-d insane来尝试更高的难度。如果你觉得很难获胜,也许你需要开发更多类型的蚂蚁。
Problem 4ThrowerAnt是一个很好的进攻单位,但如果价格便宜一点就更好了。开发ThrowerAnt的两个子类,它们拥有更低的造价,但在投掷距离上会有*:
LongThrower只能throw_at距离大于等于5的蜜蜂。对于距离小于等于4的蜜蜂*为力,当多只蜜蜂出现,也只会攻击距离满足条件的蜜蜂
ShortThrower只能throw_at距离小于等于3的蜜蜂。
以上两种蜜蜂都不能攻击距离刚好是4的蜜蜂,只放一只攻击蜜蜂是无法获胜的
ClassFood CostArmor ShortThrower21 LongThrower21一个比较好的实现方式是,让它们继承ThrowerAnt类的nearest_bee方法。因为它们选择攻击目标的逻辑是一样的,除了ShortThrower和LongThrower拥有最大和最小距离的*。
所以你需要修改ThrowerAnt中的nearest_bee方法来使用min_range和max_range属性,只返回在射程中的蜜蜂
原始ThrowerAnt没有射程的*,所以你需要为它添加对应的min_range和max_range属性,使得它不会影响ThrowerAnt,接着给子类LongThrower和ShortThrower设置合适的范围和食物开销。
提示
float('inf')返回代表无穷大的浮点数,和其他任何数字相比都要更大
别忘了将LongThrower和ShortThower的implemented属性改成True
在你开始开发之前,先完成测试:
python3?gui.py?-d?easy?--food?104完成之后,进行测试:
python3?gui.py?-d?easy?--food?105答案意思是说我们修改父类ThrowerAnt而不改子类,让父类拥有无限的射程保证父类的功能不变,又可以支持子类射程的要求。
给子类设置合适的射程,使得不需要重复实现nearest_bee方法,只需要修改配置就可以实现功能
python3?gui.py?-d?easy?--food?106Problem 5实现FireAnt类,FireAnt类拥有一个特别的方法rece_armor:当FireAnt的护甲减为0或更低时:它会攻击相同格子里的所有蜜蜂,将它们的护甲减去它的damage(默认是3)。
ClassFood CostArmor FireAnt51提示:
攻击蜜蜂会导致蜜蜂死去被移出场地,如果一遍遍历一个list,一遍移除list中的内容,可能会导致出错。
所以Python 官方教程中建议,如果你需要一遍遍历一遍修改一个序列(比如复制/删除选中的元素等),最好先将序列拷贝。你可以调用list构造函数或者使用切片诸如a[:]来完成拷贝
当你开发完成之后,将implemnted设置成True
测试之前,先答题,确保理解正确
python3?gui.py?-d?easy?--food?107开发之后,进行测试:
python3?gui.py?-d?easy?--food?108答案护甲减到0以下就攻击同一格中所有的蜜蜂,由于攻击蜜蜂可能导致蜜蜂死亡,place.bees发生变化。所以我们需要先拷贝序列,再对拷贝中的蜜蜂进行攻击。
python3?gui.py?-d?easy?--food?109可以用已有的蚂蚁玩一两局游戏了,FireAnt的伤害很高,灵活使用获胜并不难
python3?gui.py?-d?easy?--food?102Problem 6实现HungryAnt,它可以选择和它同一格的蜜蜂进行吞食。吞食完一只蜜蜂之后,需要经过3轮消化才能再次吞食。
ClassFood CostArmor HungryAnt41给HungryAnt类添加一个time_to_digest的类属性,它表示HungryAnt在吞吃之后消化需要的回合数。同样,给HungryAnt添加一个实例属性digesting标记还需要消化的回合数(默认是0,因为初始时还没有吞吃)。
实现action函数,检查是否在消化,如果是,减少digesting值,否则随机选择同一格的蜜蜂进行吞吃(将蜜蜂的护甲置为0,重置digesting)
编码之前先答题,确保理解正确
python3?ok?-q?00?-u1实现之后,进行测试:
python3?ok?-q?00?-u2答案注意类属性和实例属性的区别即可
python3?ok?-q?00?-u3Problem 7开发忍者蚂蚁(NinjaAnt),它会攻击所有经过的蜜蜂,并且不会被叮。
ClassFood CostArmor NinjaAnt51NinjaAnt不会阻碍蜜蜂的前进,要实现这一点,需要在Ant类中加上一个新的类属性blocks_path,表示是否会阻碍蜜蜂前进,在Ant中设置成True。这样的话继承自Ant的子类默认都是True,在NinjaAnt中设置成`False
其次修改Bee中的方法blocked,在没有蚂蚁拦路或者拦路的蚂蚁blocks_path为False时返回False。这样的话,蜜蜂将会从NinjaAnts身边飞过。
最后,我们希望NinjaAnt会攻击所有飞过的蜜蜂。在action函数当中实现这个功能,让它能够攻击所有在同一格的蜜蜂。和FireAnt一样,我们需要遍历可能发生改变的序列,所以需要先进行拷贝
开发前答题:
python3?ok?-q?00?-u4开发后测试:
python3?ok?-q?00?-u5尝试只用HarvestAnt和NinjaAnt获胜。
答案python3?ok?-q?00?-u6Phase 3: But They Also Protect我们现在已经有了很多进攻手段,也需要开发一些防御机制。
在这个阶段,我们将会开发一些拥有防御功能的蚂蚁,比如增加护甲,或者是保护同伴。
Problem 8我们将要开发WallAnt为蚂蚁王国提供保护,它每个回合不会做任何事情只会阻挡蜜蜂,拥有很高的护甲,类似于植物大战僵尸里的坚果。
ClassFood CostArmor WallAnt44和之前的蚂蚁不同,我们没有提供类的初始代码。
你需要从零开始实现WallAnt,将类中的name属性设置成Wall,implemented设置成True,这样图像才能正常显示。
开发前答题:
python3?ok?-q?00?-u7开发后测试:
python3?ok?-q?00?-u8答案实现WallAnt继承自Ant,设置对应的参数,并且调用父类的构造函数设置护甲(可以参考其他类)。
python3?ok?-q?00?-u9Problem 9我们的蚂蚁除了WallAnt之外都太脆弱,所以我们希望开发BodyguardAnt为蚂蚁们提供保护。
ClassFood CostArmor BodyguardAnt42BodyguardAnt和普通的蚂蚁不同,因为它是一个容器。它可以容纳一只另外的蚂蚁,并且保护它,它们在同一个Place里。当有蜜蜂攻击时,只有容器会受到攻击。
容器内的蚂蚁每回合同样可以执行action,当容器被摧毁,被保护的蚂蚁依然留存。
每个BodyguardAnt拥有一个实例属性ant,它用来存储被保护的蚂蚁。初始时因为还没有保护蚂蚁,所以是None。实现contain_ant函数,它会传入一个ant表示要被保护的ant,需要将它赋给ant属性。另外,实现BodyguardAnt的action方法,来执行被保护的ant的action。
另外,你需要做如下的改动,保证容器和被它容纳的蚂蚁一起占据同一个格子。每个格子最多两只蚂蚁,并且有一只一定是容器。
添加Ant.container类属性,表明某一个Ant的子类是否是容器。对于Ant的所有子类来说,除了BodyguardAnt以外都必须是False,除了BodyguardAnt是True。
实现Ant.can_contain方法,接收一个参数other,当满足一下条件时返回True:
当前的ant是容器
当前的ant没有容纳蚂蚁
other蚂蚁不是容器
修改Place.add_insect函数,使得它兼容容器的case,该函数接收一个参数insect,表示要放置在Place上的昆虫,考虑以下情况:
当前格子上有一个ant,并且可以容纳传入add_insect函数的insect,那么将insect容纳进ant当中
如果insect是容器,可以容纳当前格子上的ant,并且将Place.ant设置成insect
如果不满足上述条件,抛出AssertionError异常
编码前答题:
python3?ok?-q?01?-u0开发后测试:
python3?ok?-q?01?-u1答案python3?ok?-q?01?-u2Problem 10BodyguardAnt提供了很好的防御,但常言道:最好的防御是进攻。
TankAnt是一个可以进攻的容器,除了提供保护之外,它每回合还能对和它相同格子的蜜蜂产生1点伤害
ClassFood CostArmor TankAnt62你只能修改TankAnt类中的代码,如果你发现你还需要修改其他地方的代码,那么说明你之前的实现可能有点问题。想办法在不修改之前代码的前提下,完成TankAnt的功能。
答题:
python3?ok?-q?01?-u3开发后测试:
python3?ok?-q?01?-u4答案python3?ok?-q?01?-u5Phase 4: Water and Might在这个最终阶段,你需要开发一个新的格子类型和能够使用它的蚂蚁。
这些蚂蚁当中有最重要的一种:蚁后
Problem 11让我们为蚂蚁王国当中添加水池,现在我们只有两种格子:Hive和Place. 为了让游戏更有趣,让我们开发新的类型Water。
只有watersafe的蚂蚁可以在水池中存活,为了判断昆虫是否有能力在水中生存,需要在Insect类中添加一个类属性watersafe,默认设置成False。因为蜜蜂可以飞,所以蜜蜂的watersafe是True。
为Water类开发add_insect方法。首先调用Place.add_insect方法来添加昆虫,这一步不需要考虑昆虫的生存能力。如果昆虫不是防水的,那么调用rece_armor方法,将昆虫的护甲减到0。不要复制粘贴代码,使用那些已经开发好的代码。
开发前答题:
python3?ok?-q?01?-u6开发后测试:
python3?ok?-q?01?-u7当你开发完成之后,可以使用--water参数来开启水池。
python3?ok?-q?01?-u8答案在Water类当中,我们可以调用Place中的函数来完成功能,这样可以尽可能复用代码。
别忘了给对应的子类设置watersafe的属性
python3?ok?-q?01?-u9Problem 12现在没有蚂蚁可以在水中生存,所以我们要开发ScubaThrower蚂蚁,它是ThrowerAnt的子类,开销更大,但能够在水中生存,但和它的基类不同的是,ScubaThrower蚂蚁在水中不会损失护甲(蜜蜂无法下水攻击)。
ClassFood CostArmor ScubaThrower61我们没有为你提供该类的初始代码,需要从头完成