Intro
前兩個項目還算簡單, 比較不復雜. 但是今天這個第三個項目難度確實是上升了(看游戲規則就知道這個有多復雜了). 感覺像是植物大戰僵屍
所以我打算為他寫一篇博客來整理一下寫代碼時候的思路. 話不多說, 讓我們進入正題吧 !
Phase 1: Basic gameplay
Problem 1 (1 pt)
Part A: Currently, there is no cost for placing any type of
Ant, and so there is no challenge to the game. The base classAnthas afood_costof zero. Override this class attribute forHarvesterAntandThrowerAntaccording to the "Food Cost" column in the table below.Part B: Now that placing an
Antcosts food, we need to be able to gather more food! To fix this issue, implement theHarvesterAntclass. AHarvesterAntis a type ofAntthat adds one food to thegamestate.foodtotal as itsaction.
根據題目的要求設置 HarversterAnt 和 ThrowerAnt 的屬性, 同時實現 HarvesterAnt 的 action 方法, 讓它可以在每次行動的時候給 food + 1
class HarvesterAnt(Ant):
"""HarvesterAnt produces 1 additional food per turn for the colony."""
name = 'Harvester'
implemented = True
food_cost = 2
def action(self, gamestate):
"""Produce 1 additional food for the colony.
gamestate -- The GameState, used to access game state information.
"""
gamestate.food += 1
class ThrowerAnt(Ant):
"""ThrowerAnt throws a leaf each turn at the nearest Bee in its range."""
name = 'Thrower'
implemented = True
damage = 1
food_cost = 3
Problem 2 (1 pt)
In this problem, you'll complete
Place.__init__by adding code that tracks entrances. Right now, aPlacekeeps track only of itsexit. We would like aPlaceto keep track of its entrance as well. APlaceneeds to track only oneentrance. Tracking entrances will be useful when anAntneeds to see whatBees are in front of it in the tunnel.However, simply passing an entrance to a
Placeconstructor will be problematic; we would need to have both the exit and the entrance before creating aPlace! (It's a chicken or the egg problem.) To get around this problem, we will keep track of entrances in the following way instead.Place.__init__should use this logic:
- A newly created
Placealways starts with itsentranceasNone.- If the
Placehas anexit, then theexit'sentranceis set to thatPlace.
其實這個賽道有點像數據結構中的雙向鏈表的結構, 往左邊用 .exit, 往右邊用 .entrance 方法. 要求已經在上面給出, 沒什么難度
class Place:
"""A Place holds insects and has an exit to another Place."""
is_hive = False
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
if exit is not None:
self.exit.entrance = self
Problem 3 (1 pt)
In order for a
ThrowerAntto throw a leaf, it must know which bee to hit. The provided implementation of thenearest_beemethod in theThrowerAntclass only allows them to hit bees in the samePlace. Your job is to fix it so that aThrowerAntwillthrow_atthe nearest bee in front of it that is not still in theHive. This includes bees that are in the samePlaceas aThrowerAntHint: All
Places have anis_hiveattribute which isTruewhen that place is theHive.Change
nearest_beeso that it returns a randomBeefrom the nearest place that contains bees. Your implementation should follow this logic:
- Start from the current
Placeof theThrowerAnt.- For each place, return a random bee if there is any, and if not, inspect the place in front of it (stored as the current place's
entrance).- If there is no bee to attack, return
None.
現在我們要給 ThrowerAnt 加上功能, 這樣才能讓它攻擊距離它最近的蜜蜂🐝. 注意如果蜜蜂和他在同一個格子里, 也是可以攻擊的.
我們的工作要求是遍歷每個格子(就跟你遍歷鏈表一樣)找到第一個有蜜蜂的格子, 隨機返回一個蜜蜂
def nearest_bee(self):
"""Return the nearest Bee in a Place that is not the HIVE, connected to
the ThrowerAnt's Place by following entrances.
This method returns None if there is no such Bee (or none in range).
"""
pos = self.place
while pos.entrance is not None:
if not pos.is_hive:
if len(pos.bees) > 0:
return random_bee(pos.bees)
pos = pos.entrance
return None
Phase 2: Ants!
Problem 4 (2 pt)
A
ThrowerAntis a powerful threat to the bees, but it has a high food cost. In this problem, you'll implement two subclasses ofThrowerAntthat are less costly but have constraints on the distance they can throw:
- The
LongThrowercan onlythrow_ataBeethat is found after following at least 5entrancetransitions. It cannot hitBees that are in the samePlaceas it or the first 4Places in front of it. If there are twoBees, one too close to theLongThrowerand the other within its range, theLongThrowershould only throw at the fartherBee, which is within its range, instead of trying to hit the closerBee.- The
ShortThrowercan onlythrow_ataBeethat is found after following at most 3entrancetransitions. It cannot throw at any bees further than 3Places in front of it.Neither of these specialized throwers can
throw_ataBeethat is exactly 4Places away.
現在我們要實現兩個類, LongThrower 和 ShortThrower. 兩個都是 ThrowererAnt 的子類, 其實從他們的名字可以看出來他們的區別在於攻擊范圍的不同.
思路: 我們如何表示攻擊范圍這個概念呢 ? 其實很簡單, 在 problem 3 中我們找到最近的蜜蜂的時候就是一個格子一個格子前進的, 我們可以同時計算步數, 那么我們就這道這個距離, 再配合 min_range 和 max_range 這兩個參數(類變量, 表示這個類對應的螞蟻的攻擊范圍, 只有落在這個區間的才行)
同時要注意我們不能影響 problem 3 中的結果, 這也容易想到, 我們讓 min_range=-1, max_range=float('inf'), 這樣就相當於沒有限制了 ~! 而且因為面向對象程序設計的優勢, 我們省去了不少代碼量.
# In problem 3
class ThrowerAnt(Ant):
"""ThrowerAnt throws a leaf each turn at the nearest Bee in its range."""
name = 'Thrower'
implemented = True
damage = 1
food_cost = 3
min_range = -1
max_range = float('inf')
def nearest_bee(self):
"""Return the nearest Bee in a Place that is not the HIVE, connected to
the ThrowerAnt's Place by following entrances.
This method returns None if there is no such Bee (or none in range).
"""
steps_cnt = 0
pos = self.place
while pos.entrance is not None:
if steps_cnt > self.max_range:
return None
if not pos.is_hive:
if len(pos.bees) > 0 and steps_cnt >= self.min_range:
return random_bee(pos.bees)
pos = pos.entrance
steps_cnt += 1
return None
class ShortThrower(ThrowerAnt):
"""A ThrowerAnt that only throws leaves at Bees at most 3 places away."""
name = 'Short'
food_cost = 2
# OVERRIDE CLASS ATTRIBUTES HERE
implemented = True # Change to True to view in the GUI
max_range = 3
class LongThrower(ThrowerAnt):
"""A ThrowerAnt that only throws leaves at Bees at least 5 places away."""
name = 'Long'
food_cost = 2
# OVERRIDE CLASS ATTRIBUTES HERE
implemented = True # Change to True to view in the GUI
min_range = 5
Problem 5 (3 pt)
Implement the
FireAnt, which does damage when it receives damage. Specifically, if it is damaged byamounthealth units, it does a damage ofamountto all bees in its place (this is called reflected damage). If it dies, it does an additional amount of damage, as specified by itsdamageattribute, which has a default value of3as defined in theFireAntclass.To implement this, override
FireAnt'sreduce_healthmethod. Your overriden method should call thereduce_healthmethod inherited from the superclass (Ant) to reduce the currentFireAntinstance's health. Calling the inheritedreduce_healthmethod on aFireAntinstance reduces the insect'shealthby the givenamountand removes the insect from its place if itshealthreaches zero or lower.
這個 FireAnt 有點意思的, 當他收到傷害的時候會反彈自己受到的傷害到當前格子所有的蜜蜂上, 同時如果它因此死了還能對這些蜜蜂再一次造成傷害(這一次取決於自己的攻擊力)
這個比較 tricky 的地方是: 當前格子的所有蜜蜂是一個 list, 也就是我們可能要在迭代訪問 list 的時候這個這個 list, 這個我們遍歷它的拷貝即可
最后代碼如下:
class FireAnt(Ant):
"""FireAnt cooks any Bee in its Place when it expires."""
name = 'Fire'
damage = 3
food_cost = 5
implemented = True # Change to True to view in the GUI
def __init__(self, health=3):
"""Create an Ant with a HEALTH quantity."""
super().__init__(health)
def reduce_health(self, amount):
"""Reduce health by AMOUNT, and remove the FireAnt from its place if it
has no health remaining.
Make sure to reduce the health of each bee in the current place, and apply
the additional damage if the fire ant dies.
"""
# FireAnt attack bees
for bee in self.place.bees[:]:
bee.reduce_health(amount)
# FireAnt will be dead
if self.health <= amount:
for bee in self.place.bees[:]:
bee.reduce_health(self.damage)
super().reduce_health(amount)
else:
super().reduce_health(amount)
Phase 3: More Ants!
Problem 6 (1 pt)
We are going to add some protection to our glorious home base by implementing the
WallAnt, an ant that does nothing each turn. AWallAntis useful because it has a largehealthvalue.Unlike with previous ants, we have not provided you with a class header. Implement the
WallAntclass from scratch. Give it a class attributenamewith the value'Wall'(so that the graphics work) and a class attributeimplementedwith the valueTrue(so that you can use it in a game).
從零實現一個 WallAnt, 這種螞蟻生命值高, 其他倒是沒什么
class WallAnt(Ant):
"""WallAnt has a large health value"""
name = 'Wall'
damage = 0
food_cost = 4
implemented = True
def __init__(self, health=4):
super().__init__(health)
Problem 7 (3 pt)
Implement the
HungryAnt, which will select a randomBeefrom itsplaceand eat it whole. After eating aBee, aHungryAntmust spend 3 turns chewing before eating again. If there is no bee available to eat,HungryAntwill do nothing.Give
HungryAntachew_durationclass attribute that stores the number of turns that it will take aHungryAntto chew (set to 3). Also, give eachHungryAntan instance attributechew_countdownthat counts the number of turns it has left to chew (initialized to 0, since it hasn't eaten anything at the beginning. You can also think ofchew_countdownas the number of turns until aHungryAntcan eat anotherBee).Implement the
actionmethod of theHungryAnt: First, check if it is chewing; if so, decrement itschew_countdown. Otherwise, eat a randomBeein itsplaceby reducing theBee's health to 0. Make sure to set thechew_countdownwhen a Bee is eaten!
從零實現一個 HungryAnt, 可以隨機吞下一整只蜜蜂!!!!但是它要花費 chew_duration 來咀嚼才能進行下一次攻擊. 這個不就是植物大戰僵屍里面的食人花嘛!!!
我們只要判斷當前它是否處於咀嚼狀態即可. 這里題目是有挖坑的, 測試樣例里面可能在運行的時候會修改 chew_duration 的值, 注意別被坑了!
class HungryAnt(Ant):
"""HungryAnt will select a random bee from its place and eat it whole"""
name = 'Hungry'
damage = 0
food_cost = 4
implemented = True
chew_duration = 3
def __init__(self, health=1):
super().__init__(health)
self.chew_countdown = 0
def action(self, gamestate):
# it is chewing
if self.chew_countdown != 0:
self.chew_countdown -= 1
# it is not chewing
else:
if len(self.place.bees) > 0:
# WARNING: the test cases may change the chew_duration variable in runtime
self.chew_countdown = self.chew_duration
bee = random_bee(self.place.bees)
bee.reduce_health(bee.health)
Problem 8 (3 pt)
A
BodyguardAntdiffers from a normal ant because it is aContainerAnt; it can contain another ant and protect it, all in onePlace. When aBeestings the ant in aPlacewhere one ant contains another, only the container is damaged. The ant inside the container can still perform its original action. If the container perishes, the contained ant still remains in the place (and can then be damaged).Each
ContainerAnthas an instance attributeant_containedthat stores the ant it contains. This ant,ant_contained, initially starts off asNoneto indicate that there is no ant being stored yet. Implement thestore_antmethod so that it sets theContainerAnt'sant_containedinstance attribute to the passed inantargument. Also implement theContainerAnt'sactionmethod to perform itsant_contained's action if it is currently containing an ant.
這里要實現的螞蟻也很有意思, 它可以把其他螞蟻保護起來. 甚至可以和被保護的螞蟻一起呆在同一個格子里面.
這里注意幾個細節:
BodyguardAnt不能保護BodyguardAnt!(🈲️止套娃)- 當
BodyguardAnt和被保護的螞蟻在同一個格子的時候, 要讓place.ant始終指向BodyguardAnt
這里其實還涉及到挺多要改的地方的(可能會漏放某些代碼, 完整的建議看我的倉庫)
class Ant(Insect):
"""An Ant occupies a place and does work for the colony."""
implemented = False # Only implemented Ant classes should be instantiated
food_cost = 0
is_container = False
...
def add_to(self, place):
if place.ant is None:
place.ant = self
else:
assert (
(place.ant is None)
or self.can_contain(place.ant)
or place.ant.can_contain(self)
), 'Two ants in {0}'.format(place)
if place.ant.is_container and place.ant.can_contain(self):
place.ant.store_ant(self)
elif self.is_container and self.can_contain(place.ant):
self.store_ant(place.ant)
# the place.ant should refer to the container ant
place.ant = self
Insect.add_to(self, place)
class ContainerAnt(Ant):
"""
ContainerAnt can share a space with other ants by containing them.
"""
is_container = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ant_contained = None
def can_contain(self, other):
# we can't have two BodyguardAnt in the same place
if self.ant_contained is None and not other.is_container:
return True
def store_ant(self, ant):
self.ant_contained = ant
def remove_ant(self, ant):
if self.ant_contained is not ant:
assert False, "{} does not contain {}".format(self, ant)
self.ant_contained = None
def remove_from(self, place):
# Special handling for container ants (this is optional)
if place.ant is self:
# Container was removed. Contained ant should remain in the game
place.ant = place.ant.ant_contained
Insect.remove_from(self, place)
else:
# default to normal behavior
Ant.remove_from(self, place)
def action(self, gamestate):
if self.ant_contained is not None:
return self.ant_contained.action(gamestate)
class BodyguardAnt(ContainerAnt):
"""BodyguardAnt provides protection to other Ants."""
name = 'Bodyguard'
food_cost = 4
implemented = True # Change to True to view in the GUI
def __init__(self, health=2):
super().__init__(health)
Problem 9 (1 pt)
The
BodyguardAntprovides great defense, but they say the best defense is a good offense. TheTankAntis a container that protects an ant in its place and also deals 1 damage to all bees in its place each turn.We have not provided you with a class header. Implement the
TankAntclass from scratch. Give it a class attributenamewith the value'Tank'(so that the graphics work) and a class attributeimplementedwith the valueTrue(so that you can use it in a game).You should not need to modify any code outside of the
TankAntclass. If you find yourself needing to make changes elsewhere, look for a way to write your code for the previous question such that it applies not just toBodyguardAntandTankAntobjects, but to container ants in general.
根據題目的描述可以知道 TankAnt 是一種特殊的 ContainerAnt, 我們要從零實現它的功能, 它的攻擊方式比較特殊, 是自己保護的螞蟻的攻擊方式 + 對當前格子的蜜蜂造成自己攻擊力的傷害
class TankAnt(ContainerAnt):
name = 'Tank'
damage = 1
food_cost = 6
implemented = True
def __init__(self, health=2):
super().__init__(health)
def action(self, gamestate):
if self.ant_contained is not None:
self.ant_contained.action(gamestate)
# 1 damage for all the bees
for bee in self.place.bees[:]:
bee.reduce_health(self.damage)
Phase 4: Water and Might
Problem 10 (1 pt)
Let's add water to the colony! Currently there are only two types of places, the
Hiveand a basicPlace. To make things more interesting, we're going to create a new type ofPlacecalledWater.Only an insect that is waterproof can be placed in
Water. In order to determine whether anInsectis waterproof, add a new class attribute to theInsectclass namedis_waterproofthat is set toFalse. Since bees can fly, set theiris_waterproofattribute toTrue, overriding the inherited value.Now, implement the
add_insectmethod forWater. First, add the insect to the place regardless of whether it is waterproof. Then, if the insect is not waterproof, reduce the insect's health to 0. Do not repeat code from elsewhere in the program. Instead, use methods that have already been defined.
為了讓地形更有趣, 我們要增加一種地形 - Water, 只有能在水里的活動的生物才能被放在這種地形中(蜜蜂會飛當然都可以, 螞蟻目前還沒有)
記得還要修改很多類, 增加類屬性 is_waterproof, 下面我就放 Water 類的代碼
class Water(Place):
"""Water is a place that can only hold waterproof insects."""
def add_insect(self, insect):
"""Add an Insect to this place. If the insect is not waterproof, reduce
its health to 0."""
super().add_insect(insect)
if not insect.is_waterproof:
insect.reduce_health(insect.health)
Problem 11 (1 pt)
Currently there are no ants that can be placed on
Water. Implement theScubaThrower, which is a subclass ofThrowerAntthat is more costly and waterproof, but otherwise identical to its base class. AScubaThrowershould not lose its health when placed inWater.We have not provided you with a class header. Implement the
ScubaThrowerclass from scratch. Give it a class attributenamewith the value'Scuba'(so that the graphics work) and remember to set the class attributeimplementedwith the valueTrue(so that you can use it in a game).
從零實現一個 ScubaThrower, 聽名字可以看出來應該是一種特殊的 ThrowerAnt. 特殊在: 能放在 Water 里
class ScubaThrower(ThrowerAnt):
name = 'Scuba'
food_cost = 6
is_waterproof = True
implemented = True
def __init__(self, health=1):
super().__init__(health)
Problem 12 (3 pt)
Finally, implement the
QueenAnt. The queen is a waterproofScubaThrowerthat inspires her fellow ants through her bravery. In addition to the standardScubaThroweraction, theQueenAntdoubles the damage of all the ants behind her each time she performs an action. Once an ant's damage has been doubled, it is not doubled again for subsequent turns.However, with great power comes great responsibility. The
QueenAntis governed by three special rules:
- If the queen ever has its health reduced to 0, the ants lose. You will need to override
Ant.reduce_healthinQueenAntand callants_lose()in that case in order to signal to the simulator that the game is over. (The ants also still lose if any bee reaches the end of a tunnel.)- There can be only one queen. A second queen cannot be constructed. To check if an Ant can be constructed, we use the
Ant.construct()class method to either construct an Ant if possible, or returnNoneif not. You will need to overrideAnt.constructas a class method ofQueenAntin order to add this check. To keep track of whether a queen has already been created, you can use an instance variable added to the currentGameState.- The queen cannot be removed. Attempts to remove the queen should have no effect (but should not cause an error). You will need to override
Ant.remove_frominQueenAntto enforce this condition.
終於來到了最后一題(除了額外的題目以外), 我們要實現一個女王蟻🐜. 它有以下幾個特性:
- 可以在水中行走
- 思路: 題目也說了它是一種
ScrubaThrower, 根據這個描述其實就抽象概括出了它是ScrubaThrower的子類.
- 思路: 題目也說了它是一種
- 它在行動后會把在它后面的螞蟻們的攻擊力都翻倍, 但是不可以多次翻倍
- 思路: 如何表示 “在后面” 這個關系 ? 根據前面的題目我們可以知道. 右邊為正方向, 所以 “在后面” 實際上就是在左邊, 我們可以通過訪問
Place的.exit方法不斷獲取到它左邊(后面的)的 - 思路: 如何表示不能多次翻倍 ? 很容易想到, 我們需要通過設置一個標記來表示當前的螞蟻是否已經攻擊力翻倍過, 所以我們直接在
Ant類里加一個實例變量即可 - 思路: 這里還要注意如何處理 GuardAnt, 因為實際上它守護的螞蟻是可能被替換為新的螞蟻, 此時我們就要讓這個新的被守護的螞蟻的攻擊力翻倍. 注意細細體會這里代碼是怎么寫的
- 思路: 如何表示 “在后面” 這個關系 ? 根據前面的題目我們可以知道. 右邊為正方向, 所以 “在后面” 實際上就是在左邊, 我們可以通過訪問
- 只能有一個女王蟻🐜
- 思路: 如何做到即使我們多次調用女王蟻🐜的構造函數也不會有多的女王蟻🐜 ? 這個依賴於一個游戲變量叫做
gamestate, 我們仍然是通過加標記的方式, 只不過這次我們是在GameState這個類里加一個has_queen表示當前是否已經有女王蟻🐜
- 思路: 如何做到即使我們多次調用女王蟻🐜的構造函數也不會有多的女王蟻🐜 ? 這個依賴於一個游戲變量叫做
- 女王蟻🐜不能被移除
- 思路: 這個簡單, 我們什么都不做就行了
最后代碼大概如下(結合上面的解釋細細體會~~)
class QueenAnt(ScubaThrower):
"""The Queen of the colony. The game is over if a bee enters her place."""
name = 'Queen'
food_cost = 7
implemented = True # Change to True to view in the GUI
@classmethod
def construct(cls, gamestate):
"""
Returns a new instance of the Ant class if it is possible to construct, or
returns None otherwise. Remember to call the construct() method of the superclass!
"""
if cls.food_cost > gamestate.food:
print('Not enough food remains to place ' + cls.__name__)
return
# I add a class variable to indict if we have created a QueenAnt()
if not gamestate.has_queen:
gamestate.has_queen = True
return super().construct(gamestate)
else:
return None
def action(self, gamestate):
"""A queen ant throws a leaf, but also doubles the damage of ants
in her tunnel.
"""
super().action(gamestate)
pos = self.place.exit
while pos:
if pos.ant is not None:
if not pos.ant.is_doubled:
pos.ant.is_doubled = True
pos.ant.buff()
if pos.ant.is_container and pos.ant.ant_contained is not None:
# the pos.ant.ant_contained may change
if not pos.ant.ant_contained.is_doubled:
pos.ant.ant_contained.buff()
pos.ant.ant_contained.is_doubled = True
pos = pos.exit
def reduce_health(self, amount):
"""Reduce health by AMOUNT, and if the QueenAnt has no health
remaining, signal the end of the game.
"""
if self.health <= amount:
ants_lose()
def remove_from(self, place):
return None
Extra Credit (2 pt)
Implement two final thrower ants that do zero damage, but instead apply a temporary "status" on the
actionmethod of aBeeinstance that theythrow_at. This "status" lasts for a certain number of turns, after which it ceases to take effect.We will be implementing two new ants that inherit from
ThrowerAnt.
SlowThrowerthrows sticky syrup at a bee, slowing it for 3 turns. When a bee is slowed, it can only move on turns whengamestate.timeis even, and can do nothing otherwise. If a bee is hit by syrup while it is already slowed, it is slowed for an additional 3 turns.ScaryThrowerintimidates a nearby bee, causing it to back away instead of advancing. (If the bee is already right next to the Hive and cannot go back further, it should not move. To check if a bee is next to the Hive, you might find theis_hiveinstance attribute ofPlaces useful). Bees remain scared until they have tried to back away twice. Bees cannot try to back away if they are slowed andgamestate.timeis odd. Once a bee has been scared once, it can't be scared ever again.
實現兩種特殊的螞蟻類, 本身沒有傷害, 但是能給蜜蜂加上 debuff.
SlowThrower可以讓蜜蜂減速, 讓他們只能在當前時間為偶數的時候前進否則什么事情也干不了. 這個效果可以維持三個回合, 但是這個 debuff 可以無限疊加ScaryThrower會讓蜜蜂后退, 注意如果不能再后退的話, 就要保持不動. 該效果維持兩回合. 但是如果被減速就會繼續保持不動, 這個 debuff 只能上一次
這一題, 真的, 難度完全是上來了. 我調了挺久的 bug 才成功. 下面我來講一下設計思路:
SlowThrower
- 設置
is_slow變量表示當前的蜜蜂是否被減速, 同時設置slow_turns來記住剩余幾回合可以解除這個狀態 - 每個回合, 如果當前的蜜蜂被減速了, 它只能看當前的游戲時間是否為偶數, 如果是的話就可以前進, 否則在原地不動, **但不論你動不動,
slow_turns -= 1永遠都成立
ScaryThrower
-
類似
is_slow和slow_turns設置了is_scared和scared_turns -
我們暫時先不考慮當前蜜蜂是否被減速了(這樣思考問題會比較簡單). 顯然, 我們每回合要做的事情是讓
scared_turns -= 1, 然后是否為 scared 其實決定着蜜蜂的前進方向. 有了這個基礎之后我們再來思考被減速帶情況下又該如何, 顯然我們前面這樣是有問題的, 題目說了如果被減速會原地保持不動, 但是我們卻讓scared_turns -= 1, 所以我們需要多加一個判斷, 也就是被減速 + 被 scared 的情況下如果我們沒有成功移動, 那么我們需要撤銷我們對scared_turns的更改
知道了上面的設計思路, 理解下面的代碼就不難了(這里刪去了無關的代碼):
class Bee(Insect):
"""A Bee moves from place to place, following exits and stinging ants."""
name = 'Bee'
damage = 1
is_waterproof = True
# 2 flags
is_slow = False
is_scared = False
# turns remained
slow_turns = 0
scared_turns = 0
# we can't scare a bee twice
has_been_scared = False
def action(self, gamestate):
"""A Bee's action stings the Ant that blocks its exit if it is blocked,
or moves to the exit of its current place otherwise.
gamestate -- The GameState, used to access game state information.
"""
if self.is_scared:
destination = self.place.entrance
self.scared_turns -= 1
else:
destination = self.place.exit
if self.is_slow:
self.slow_turns -= 1
if self.slow_turns == 0:
self.is_slow = False
if gamestate.time % 2 == 0 and self.health > 0 and destination is not None:
self.move_to(destination)
elif self.is_scared:
# is_slow + is_scared, we need to cancel `self.scared_turns -= 1` \
# if we didn't move
self.scared_turns += 1
else:
if self.blocked():
self.sting(self.place.ant)
elif self.health > 0 and destination is not None:
self.move_to(destination)
# we can't put this in side `if self.is_scared`, why?
# because only when we run if self.is_slow we can know
# should we cancel it or not
if self.scared_turns == 0:
self.is_scared = False
# Extra credit: Special handling for bee direction
def slow(self, length):
"""Slow the bee for a further LENGTH turns."""
self.is_slow = True
self.slow_turns += length
def scare(self, length):
"""
If this Bee has not been scared before, cause it to attempt to
go backwards LENGTH times.
"""
# a bee can't be scared twice
if self.has_been_scared:
return
else:
self.is_scared = True
self.scared_turns += length
self.has_been_scared = True
Optional Problems
Optional Problem 1
Implement the
NinjaAnt, which damages allBees that pass by, but can never be stung.A
NinjaAntdoes not block the path of aBeethat flies by. To implement this behavior, first modify theAntclass to include a new class attributeblocks_paththat is set toTrue, then override the value ofblocks_pathtoFalsein theNinjaAntclass.Second, modify the
Bee's methodblockedto returnFalseif either there is noAntin theBee'splaceor if there is anAnt, but itsblocks_pathattribute isFalse. NowBees will just fly pastNinjaAnts.Finally, we want to make the
NinjaAntdamage allBees that fly past. Implement theactionmethod inNinjaAntto reduce the health of allBees in the sameplaceas theNinjaAntby itsdamageattribute. Similar to theFireAnt, you must iterate over a potentially changing list of bees.
忍者蟻🥷🐜哈哈哈哈, 注意幾個細節:
- 無法被蜜蜂攻擊
- 不會堵住蜜蜂的路, 但是會對經過的蜜蜂造成傷害
這個問題比較簡單, 解法也幾乎都寫在了問題描述里面
class Bee(Insect):
"""A Bee moves from place to place, following exits and stinging ants."""
def blocked(self):
"""Return True if this Bee cannot advance to the next Place."""
if self.place.ant is None:
return False
if not self.place.ant.blocks_path:
return False
return True
class NinjaAnt(Ant):
"""NinjaAnt does not block the path and damages all bees in its place.
This class is optional.
"""
name = 'Ninja'
damage = 1
food_cost = 5
blocks_path = False
implemented = True # Change to True to view in the GUI
def action(self, gamestate):
for bee in self.place.bees[:]:
bee.reduce_health(self.damage)
Optional Problem 2
The
LaserAntshoots out a powerful laser, damaging all that dare to stand in its path. BothBees andAnts, of all types, are at risk of being damaged byLaserAnt. When aLaserAnttakes its action, it will damage allInsects in its place (excluding itself, but including its container if it has one) and thePlaces in front of it, excluding theHive.If that were it,
LaserAntwould be too powerful for us to contain. TheLaserAnthas a base damage of2. But,LaserAnt's laser comes with some quirks. The laser is weakened by0.25each place it travels away fromLaserAnt's place. Additionally,LaserAnthas limited battery. Each timeLaserAntactually damages anInsectits laser's total damage goes down by0.0625(1/16). IfLaserAnt's damage becomes negative due to these restrictions, it simply does 0 damage instead.
激光🐜, 注意幾個特性:
- 傷害自己格子所在的所有生物, 甚至包括整條路徑上的所有生物
- 但是每次對其他生物造成傷害的時候傷害會衰減, 每次減去
0.0625 - 激光的威力跟它離激光蟻🐜的距離也有關系, 距離每多一個格子, 就會減去
0.25
只要處理好兩個函數即可
calculate_damage: 這個要注意的地方是, 如果算出來的傷害 < 0, 那么你就需要返回 0insects_in_front: 這個要返回一個dict表示每個生物距離激光🐜的距離. 我這了是分成當前格子和剩下的格子這樣來處理, 一邊遍歷所有格子一邊計算距離和把生物加到我們的dict里.
class LaserAnt(ThrowerAnt):
name = 'Laser'
food_cost = 10
implemented = True # Change to True to view in the GUI
damage = 2
def __init__(self, health=1):
super().__init__(health)
self.insects_shot = 0
self.current_damage = LaserAnt.damage
def insects_in_front(self):
"""Return a dict contains every Insect"""
dis = {}
for bee in self.place.bees:
dis[bee] = 0
# take care of the ContainerAnt
if self.place.ant is not self:
dis[self.place.ant] = 0
pos = self.place.entrance
distance = 1
while pos.entrance is not None:
if not pos.is_hive:
for bee in pos.bees:
dis[bee] = distance
if pos.ant is not None:
dis[pos.ant] = distance
# take care of the ContainerAnt
if pos.ant.is_container and pos.ant.ant_contained is not None:
dis[pos.ant.ant_contained] = distance
distance += 1
pos = pos.entrance
return dis
def calculate_damage(self, distance):
damage_result = self.damage - 0.0625 * self.insects_shot - 0.25 * distance
return damage_result if damage_result > 0 else 0
def action(self, gamestate):
insects_and_distances = self.insects_in_front()
for insect, distance in insects_and_distances.items():
damage = self.calculate_damage(distance)
insect.reduce_health(damage)
if damage:
self.insects_shot += 1
