Advertisement
Guest User

Untitled

a guest
Mar 3rd, 2020
2,205
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.97 KB | None | 0 0
  1. """CS 61A presents Ants Vs. SomeBees."""
  2.  
  3. import random
  4. from ucb import main, interact, trace
  5. from collections import OrderedDict
  6.  
  7. ################
  8. # Core Classes #
  9. ################
  10.  
  11. class Place:
  12. """A Place holds insects and has an exit to another Place."""
  13.  
  14. def __init__(self, name, exit=None):
  15. """Create a Place with the given NAME and EXIT.
  16.  
  17. name -- A string; the name of this Place.
  18. exit -- The Place reached by exiting this Place (may be None).
  19. """
  20. self.name = name
  21. self.exit = exit
  22. self.bees = [] # A list of Bees
  23. self.ant = None # An Ant
  24. self.entrance = None # A Place
  25. # Phase 1: Add an entrance to the exit
  26. # BEGIN Problem 2
  27. if exit is not None:
  28. exit.entrance = self
  29. # END Problem 2
  30.  
  31. def add_insect(self, insect):
  32. """
  33. Asks the insect to add itself to the current place. This method exists so
  34. it can be enhanced in subclasses.
  35. """
  36. insect.add_to(self)
  37.  
  38. def remove_insect(self, insect):
  39. """
  40. Asks the insect to remove itself from the current place. This method exists so
  41. it can be enhanced in subclasses.
  42. """
  43. insect.remove_from(self)
  44.  
  45. def __str__(self):
  46. return self.name
  47.  
  48.  
  49. class Insect:
  50. """An Insect, the base class of Ant and Bee, has armor and a Place."""
  51.  
  52. damage = 0
  53. # ADD CLASS ATTRIBUTES HERE
  54.  
  55. def __init__(self, armor, place=None):
  56. """Create an Insect with an ARMOR amount and a starting PLACE."""
  57. self.armor = armor
  58. self.place = place # set by Place.add_insect and Place.remove_insect
  59.  
  60. def reduce_armor(self, amount):
  61. """Reduce armor by AMOUNT, and remove the insect from its place if it
  62. has no armor remaining.
  63.  
  64. >>> test_insect = Insect(5)
  65. >>> test_insect.reduce_armor(2)
  66. >>> test_insect.armor
  67. 3
  68. """
  69. self.armor -= amount
  70. if self.armor <= 0:
  71. self.place.remove_insect(self)
  72. self.death_callback()
  73.  
  74. def action(self, gamestate):
  75. """The action performed each turn.
  76.  
  77. gamestate -- The GameState, used to access game state information.
  78. """
  79.  
  80. def death_callback(self):
  81. # overriden by the gui
  82. pass
  83.  
  84. def add_to(self, place):
  85. """Add this Insect to the given Place
  86.  
  87. By default just sets the place attribute, but this should be overriden in the subclasses
  88. to manipulate the relevant attributes of Place
  89. """
  90. self.place = place
  91.  
  92. def remove_from(self, place):
  93. self.place = None
  94.  
  95.  
  96. def __repr__(self):
  97. cname = type(self).__name__
  98. return '{0}({1}, {2})'.format(cname, self.armor, self.place)
  99.  
  100.  
  101. class Ant(Insect):
  102. """An Ant occupies a place and does work for the colony."""
  103.  
  104. implemented = False # Only implemented Ant classes should be instantiated
  105. food_cost = 0
  106. blocks_path = True
  107. # ADD CLASS ATTRIBUTES HERE
  108.  
  109. def __init__(self, armor=1):
  110. """Create an Ant with an ARMOR quantity."""
  111. Insect.__init__(self, armor)
  112.  
  113. def can_contain(self, other):
  114. return False
  115.  
  116. def contain_ant(self, other):
  117. assert False, "{0} cannot contain an ant".format(self)
  118.  
  119. def remove_ant(self, other):
  120. assert False, "{0} cannot contain an ant".format(self)
  121.  
  122. def add_to(self, place):
  123. if place.ant is None:
  124. place.ant = self
  125. else:
  126. # BEGIN Problem 9
  127. if self.ant.can_contain(insect):
  128. self.ant.contain_ant(insect)
  129. elif insect.can_contain(self.ant):
  130. insect.contain_ant(self.ant)
  131. self.ant = insect
  132. else:
  133. assert self.ant is None, 'Two ants in {0}'.format(self)
  134. # END Problem 9
  135. Insect.add_to(self, place)
  136.  
  137. def remove_from(self, place):
  138. if place.ant is self:
  139. place.ant = None
  140. elif place.ant is None:
  141. assert False, '{0} is not in {1}'.format(self, place)
  142. else:
  143. # container or other situation
  144. place.ant.remove_ant(self)
  145. Insect.remove_from(self, place)
  146.  
  147. class HarvesterAnt(Ant):
  148. """HarvesterAnt produces 1 additional food per turn for the colony."""
  149.  
  150. name = 'Harvester'
  151. implemented = True
  152. food_cost = 2
  153. # OVERRIDE CLASS ATTRIBUTES HERE
  154.  
  155. def action(self, gamestate):
  156. """Produce 1 additional food for the colony.
  157.  
  158. gamestate -- The GameState, used to access game state information.
  159. """
  160. # BEGIN Problem 1
  161. gamestate.food += 1
  162. # END Problem 1
  163.  
  164.  
  165. class ThrowerAnt(Ant):
  166. """ThrowerAnt throws a leaf each turn at the nearest Bee in its range."""
  167.  
  168. name = 'Thrower'
  169. implemented = True
  170. damage = 1
  171. food_cost = 3
  172. min_range = 0
  173. max_range = float('inf')
  174. # ADD/OVERRIDE CLASS ATTRIBUTES HERE
  175.  
  176. def nearest_bee(self, beehive):
  177. """Return the nearest Bee in a Place that is not the HIVE, connected to
  178. the ThrowerAnt's Place by following entrances.
  179.  
  180. This method returns None if there is no such Bee (or none in range).
  181. """
  182. # BEGIN Problem 3 and 4
  183. count = 0
  184. current_place = self.place
  185. while current_place.entrance:
  186. if current_place.bees:
  187. if count >= self.min_range and count <= self.max_range:
  188. return random_or_none(current_place.bees)
  189. current_place = current_place.entrance
  190. count +=1
  191. return None
  192. # END Problem 3 and 4
  193.  
  194. def throw_at(self, target):
  195. """Throw a leaf at the TARGET Bee, reducing its armor."""
  196. if target is not None:
  197. target.reduce_armor(self.damage)
  198.  
  199. def action(self, gamestate):
  200. """Throw a leaf at the nearest Bee in range."""
  201. self.throw_at(self.nearest_bee(gamestate.beehive))
  202.  
  203. def random_or_none(s):
  204. """Return a random element of sequence S, or return None if S is empty."""
  205. assert isinstance(s, list), "random_or_none's argument should be a list but was a %s" % type(s).__name__
  206. if s:
  207. return random.choice(s)
  208.  
  209. ##############
  210. # Extensions #
  211. ##############
  212.  
  213. class ShortThrower(ThrowerAnt):
  214. """A ThrowerAnt that only throws leaves at Bees at most 3 places away."""
  215.  
  216. name = 'Short'
  217. food_cost = 2
  218. max_range = 3
  219. # OVERRIDE CLASS ATTRIBUTES HERE
  220. # BEGIN Problem 4
  221. def nearest_bee(self, hive):
  222. return ThrowerAnt.nearest_bee(self, hive)
  223. return None
  224. implemented = True # Change to True to view in the GUI
  225. # END Problem 4
  226.  
  227. class LongThrower(ThrowerAnt):
  228. """A ThrowerAnt that only throws leaves at Bees at least 5 places away."""
  229.  
  230. name = 'Long'
  231. food_cost = 2
  232. min_range = 5
  233. # OVERRIDE CLASS ATTRIBUTES HERE
  234. # BEGIN Problem 4
  235. def nearest_bee(self,hive):
  236. return ThrowerAnt.nearest_bee(self, hive)
  237. implemented = True
  238. # Change to True to view in the GUI
  239. # END Problem 4
  240.  
  241. class FireAnt(Ant):
  242. """FireAnt cooks any Bee in its Place when it expires."""
  243.  
  244. name = 'Fire'
  245. damage = 3
  246. food_cost = 5
  247. # OVERRIDE CLASS ATTRIBUTES HERE
  248. # BEGIN Problem 5
  249. name = 'Fire'
  250. damage = 3
  251. armor = 1
  252. implemented = True # Change to True to view in the GUI
  253. # END Problem 5
  254.  
  255. def __init__(self, armor=3):
  256. """Create an Ant with an ARMOR quantity."""
  257. Ant.__init__(self, armor)
  258.  
  259. def reduce_armor(self, amount):
  260. """Reduce armor by AMOUNT, and remove the FireAnt from its place if it
  261. has no armor remaining.
  262.  
  263. Make sure to damage each bee in the current place, and apply the bonus
  264. if the fire ant dies.
  265. """
  266. # BEGIN Problem 5
  267. self.armor -= amount
  268. bees_lst = self.place.bees[:]
  269. for bee in bees_lst[:]:
  270. bee.reduce_armor(amount)
  271. if self.armor <= 0:
  272. bees_lst = self.place.bees[:]
  273. for bee in bees_lst[:]:
  274. bee.reduce_armor(self.damage)
  275. self.place.remove_insect(self)
  276. # END Problem 5
  277.  
  278. class HungryAnt(Ant):
  279. """HungryAnt will take three turns to digest a Bee in its place.
  280. While digesting, the HungryAnt can't eat another Bee.
  281. """
  282. name = 'Hungry'
  283. food_cost = 4
  284. # OVERRIDE CLASS ATTRIBUTES HERE
  285. # BEGIN Problem 6
  286.  
  287. name = 'Hungry'
  288. food_cost = 4
  289. time_to_digest = 3
  290. implemented = True # Change to True to view in the GUI
  291. # END Problem 6
  292.  
  293. def __init__(self, armor=1):
  294. # BEGIN Problem 6
  295. Ant.__init__(self, armor)
  296. self.digesting = 0
  297. # END Problem 6
  298.  
  299. def eat_bee(self, bee):
  300. # BEGIN Problem 6
  301. bee.reduce_armor(bee.armor)
  302. self.digesting = self.time_to_digest
  303. # END Problem 6
  304.  
  305. def action(self, colony):
  306. # BEGIN Problem 6
  307. if self.digesting:
  308. self.digesting -=1
  309. else:
  310. if self.place.bees:
  311. self.eat_bee(random_or_none(self.place.bees))
  312. # END Problem 6
  313.  
  314. class NinjaAnt(Ant):
  315. """NinjaAnt does not block the path and damages all bees in its place."""
  316.  
  317. name = 'Ninja'
  318. damage = 1
  319. food_cost = 5
  320. # OVERRIDE CLASS ATTRIBUTES HERE
  321. # BEGIN Problem 7
  322. food_cost = 5
  323. armor = 1
  324. name = 'Ninja'
  325. damage = 1
  326. blocks_path = False
  327. implemented = True # Change to True to view in the GUI # Change to True to view in the GUI
  328. # END Problem 7
  329.  
  330. def action(self, gamestate):
  331. # BEGIN Problem 7
  332. Bees_in_place = list(self.place.bees)
  333. for bee in Bees_in_place:
  334. bee.reduce_armor(self.damage)
  335. # END Problem 7
  336.  
  337. # BEGIN Problem 8
  338. class WallAnt(Ant):
  339. name = 'Wall'
  340. food_cost = 4
  341. implemented = True
  342.  
  343. def __init__(self, armor=4):
  344. Ant.__init__(self, armor)
  345. # END Problem 8
  346.  
  347. class ContainerAnt(Ant):
  348. def __init__(self, *args, **kwargs):
  349. Ant.__init__(self, *args, **kwargs)
  350. self.contained_ant = None
  351.  
  352. def can_contain(self, other):
  353. # BEGIN Problem 9
  354. if self.container and not self.ant and not other.container:
  355. return True
  356. else:
  357. return False
  358. # END Problem 9
  359.  
  360. def contain_ant(self, ant):
  361. # BEGIN Problem 9
  362. self.ant = ant
  363. # END Problem 9
  364.  
  365. def remove_ant(self, ant):
  366. if self.contained_ant is not ant:
  367. assert False, "{} does not contain {}".format(self, ant)
  368. self.contained_ant = None
  369.  
  370. def remove_from(self, place):
  371. # Special handling for container ants
  372. if place.ant is self:
  373. # Container was removed. Contained ant should remain in the game
  374. place.ant = place.ant.contained_ant
  375. Insect.remove_from(self, place)
  376. else:
  377. # default to normal behavior
  378. Ant.remove_from(self, place)
  379.  
  380. def action(self, gamestate):
  381. # BEGIN Problem 9
  382. if self.ant:
  383. Ant.action(self.ant.action(gamestate), gamestate)
  384. # END Problem 9
  385.  
  386. class BodyguardAnt(ContainerAnt):
  387. """BodyguardAnt provides protection to other Ants."""
  388.  
  389. name = 'Bodyguard'
  390. food_cost = 4
  391. container = True
  392. # OVERRIDE CLASS ATTRIBUTES HERE
  393. # BEGIN Problem 9
  394. def __init__(self, armor=2):
  395. Ant.__init__(self, armor)
  396. self.contained_ant = None # The Ant hidden in this bodyguard
  397. implemented = True # Change to True to view in the GUI
  398. # END Problem 9
  399.  
  400. class TankAnt(ContainerAnt):
  401. """TankAnt provides both offensive and defensive capabilities."""
  402.  
  403. name = 'Tank'
  404. damage = 1
  405. food_cost = 6
  406. # OVERRIDE CLASS ATTRIBUTES HERE
  407. # BEGIN Problem 10
  408. implemented = False # Change to True to view in the GUI
  409. # END Problem 10
  410.  
  411. def __init__(self, armor=2):
  412. ContainerAnt.__init__(self, armor)
  413.  
  414. def action(self, gamestate):
  415. # BEGIN Problem 10
  416. food_cost = 6
  417. armor = 2
  418. implemented = True
  419. container = True
  420. # END Problem 10
  421.  
  422. class Water(Place):
  423. """Water is a place that can only hold watersafe insects."""
  424.  
  425. def add_insect(self, insect):
  426. """Add an Insect to this place. If the insect is not watersafe, reduce
  427. its armor to 0."""
  428. # BEGIN Problem 11
  429. "*** YOUR CODE HERE ***"
  430. # END Problem 11
  431.  
  432. # BEGIN Problem 12
  433. # The ScubaThrower class
  434. # END Problem 12
  435.  
  436. # BEGIN Problem 13
  437. class QueenAnt(Ant): # You should change this line
  438. # END Problem 13
  439. """The Queen of the colony. The game is over if a bee enters her place."""
  440.  
  441. name = 'Queen'
  442. food_cost = 7
  443. # OVERRIDE CLASS ATTRIBUTES HERE
  444. # BEGIN Problem 13
  445. implemented = False # Change to True to view in the GUI
  446. # END Problem 13
  447.  
  448. def __init__(self, armor=1):
  449. # BEGIN Problem 13
  450. "*** YOUR CODE HERE ***"
  451. # END Problem 13
  452.  
  453. def action(self, gamestate):
  454. """A queen ant throws a leaf, but also doubles the damage of ants
  455. in her tunnel.
  456.  
  457. Impostor queens do only one thing: reduce their own armor to 0.
  458. """
  459. # BEGIN Problem 13
  460. "*** YOUR CODE HERE ***"
  461. # END Problem 13
  462.  
  463. def reduce_armor(self, amount):
  464. """Reduce armor by AMOUNT, and if the True QueenAnt has no armor
  465. remaining, signal the end of the game.
  466. """
  467. # BEGIN Problem 13
  468. "*** YOUR CODE HERE ***"
  469. # END Problem 13
  470.  
  471.  
  472.  
  473. class AntRemover(Ant):
  474. """Allows the player to remove ants from the board in the GUI."""
  475.  
  476. name = 'Remover'
  477. implemented = False
  478.  
  479. def __init__(self):
  480. Ant.__init__(self, 0)
  481.  
  482. class Bee(Insect):
  483. """A Bee moves from place to place, following exits and stinging ants."""
  484.  
  485. name = 'Bee'
  486. damage = 1
  487. # OVERRIDE CLASS ATTRIBUTES HERE
  488.  
  489.  
  490. def sting(self, ant):
  491. """Attack an ANT, reducing its armor by 1."""
  492. ant.reduce_armor(self.damage)
  493.  
  494. def move_to(self, place):
  495. """Move from the Bee's current Place to a new PLACE."""
  496. self.place.remove_insect(self)
  497. place.add_insect(self)
  498.  
  499. def blocked(self):
  500. """Return True if this Bee cannot advance to the next Place."""
  501. # Phase 4: Special handling for NinjaAnt
  502. # BEGIN Problem 7
  503. if not self.place.ant:
  504. return False
  505. elif not self.place.ant.blocks_path:
  506. return False
  507. return True
  508. # END Problem 7
  509.  
  510. def action(self, gamestate):
  511. """A Bee's action stings the Ant that blocks its exit if it is blocked,
  512. or moves to the exit of its current place otherwise.
  513.  
  514. gamestate -- The GameState, used to access game state information.
  515. """
  516. destination = self.place.exit
  517. # Extra credit: Special handling for bee direction
  518. # BEGIN EC
  519. "*** YOUR CODE HERE ***"
  520. # END EC
  521. if self.blocked():
  522. self.sting(self.place.ant)
  523. elif self.armor > 0 and destination is not None:
  524. self.move_to(destination)
  525.  
  526. def add_to(self, place):
  527. place.bees.append(self)
  528. Insect.add_to(self, place)
  529.  
  530. def remove_from(self, place):
  531. place.bees.remove(self)
  532. Insect.remove_from(self, place)
  533.  
  534. ##################
  535. # Status Effects #
  536. ##################
  537.  
  538. def make_slow(action, bee):
  539. """Return a new action method that calls ACTION every other turn.
  540.  
  541. action -- An action method of some Bee
  542. """
  543. # BEGIN Problem EC
  544. "*** YOUR CODE HERE ***"
  545. # END Problem EC
  546.  
  547. def make_scare(action, bee):
  548. """Return a new action method that makes the bee go backwards.
  549.  
  550. action -- An action method of some Bee
  551. """
  552. # BEGIN Problem EC
  553. "*** YOUR CODE HERE ***"
  554. # END Problem EC
  555.  
  556. def apply_effect(effect, bee, duration):
  557. """Apply a status effect to a BEE that lasts for DURATION turns."""
  558. # BEGIN Problem EC
  559. "*** YOUR CODE HERE ***"
  560. # END Problem EC
  561.  
  562.  
  563. class SlowThrower(ThrowerAnt):
  564. """ThrowerAnt that causes Slow on Bees."""
  565.  
  566. name = 'Slow'
  567. food_cost = 4
  568. # BEGIN Problem EC
  569. implemented = False # Change to True to view in the GUI
  570. # END Problem EC
  571.  
  572. def throw_at(self, target):
  573. if target:
  574. apply_effect(make_slow, target, 3)
  575.  
  576.  
  577. class ScaryThrower(ThrowerAnt):
  578. """ThrowerAnt that intimidates Bees, making them back away instead of advancing."""
  579.  
  580. name = 'Scary'
  581. food_cost = 6
  582. # BEGIN Problem EC
  583. implemented = False # Change to True to view in the GUI
  584. # END Problem EC
  585.  
  586. def throw_at(self, target):
  587. # BEGIN Problem EC
  588. "*** YOUR CODE HERE ***"
  589. # END Problem EC
  590.  
  591. class LaserAnt(ThrowerAnt):
  592. # This class is optional. Only one test is provided for this class.
  593.  
  594. name = 'Laser'
  595. food_cost = 10
  596. # OVERRIDE CLASS ATTRIBUTES HERE
  597. # BEGIN Problem OPTIONAL
  598. implemented = False # Change to True to view in the GUI
  599. # END Problem OPTIONAL
  600.  
  601. def __init__(self, armor=1):
  602. ThrowerAnt.__init__(self, armor)
  603. self.insects_shot = 0
  604.  
  605. def insects_in_front(self, beehive):
  606. # BEGIN Problem OPTIONAL
  607. return {}
  608. # END Problem OPTIONAL
  609.  
  610. def calculate_damage(self, distance):
  611. # BEGIN Problem OPTIONAL
  612. return 0
  613. # END Problem OPTIONAL
  614.  
  615. def action(self, gamestate):
  616. insects_and_distances = self.insects_in_front(gamestate.beehive)
  617. for insect, distance in insects_and_distances.items():
  618. damage = self.calculate_damage(distance)
  619. insect.reduce_armor(damage)
  620. if damage:
  621. self.insects_shot += 1
  622.  
  623.  
  624. ##################
  625. # Bees Extension #
  626. ##################
  627.  
  628. class Wasp(Bee):
  629. """Class of Bee that has higher damage."""
  630. name = 'Wasp'
  631. damage = 2
  632.  
  633. class Hornet(Bee):
  634. """Class of bee that is capable of taking two actions per turn, although
  635. its overall damage output is lower. Immune to status effects.
  636. """
  637. name = 'Hornet'
  638. damage = 0.25
  639.  
  640. def action(self, gamestate):
  641. for i in range(2):
  642. if self.armor > 0:
  643. super().action(gamestate)
  644.  
  645. def __setattr__(self, name, value):
  646. if name != 'action':
  647. object.__setattr__(self, name, value)
  648.  
  649. class NinjaBee(Bee):
  650. """A Bee that cannot be blocked. Is capable of moving past all defenses to
  651. assassinate the Queen.
  652. """
  653. name = 'NinjaBee'
  654.  
  655. def blocked(self):
  656. return False
  657.  
  658. class Boss(Wasp, Hornet):
  659. """The leader of the bees. Combines the high damage of the Wasp along with
  660. status effect immunity of Hornets. Damage to the boss is capped up to 8
  661. damage by a single attack.
  662. """
  663. name = 'Boss'
  664. damage_cap = 8
  665. action = Wasp.action
  666.  
  667. def reduce_armor(self, amount):
  668. super().reduce_armor(self.damage_modifier(amount))
  669.  
  670. def damage_modifier(self, amount):
  671. return amount * self.damage_cap/(self.damage_cap + amount)
  672.  
  673. class Hive(Place):
  674. """The Place from which the Bees launch their assault.
  675.  
  676. assault_plan -- An AssaultPlan; when & where bees enter the colony.
  677. """
  678.  
  679. def __init__(self, assault_plan):
  680. self.name = 'Hive'
  681. self.assault_plan = assault_plan
  682. self.bees = []
  683. for bee in assault_plan.all_bees:
  684. self.add_insect(bee)
  685. # The following attributes are always None for a Hive
  686. self.entrance = None
  687. self.ant = None
  688. self.exit = None
  689.  
  690. def strategy(self, gamestate):
  691. exits = [p for p in gamestate.places.values() if p.entrance is self]
  692. for bee in self.assault_plan.get(gamestate.time, []):
  693. bee.move_to(random.choice(exits))
  694. gamestate.active_bees.append(bee)
  695.  
  696.  
  697. class GameState:
  698. """An ant collective that manages global game state and simulates time.
  699.  
  700. Attributes:
  701. time -- elapsed time
  702. food -- the colony's available food total
  703. places -- A list of all places in the colony (including a Hive)
  704. bee_entrances -- A list of places that bees can enter
  705. """
  706.  
  707. def __init__(self, strategy, beehive, ant_types, create_places, dimensions, food=2):
  708. """Create an GameState for simulating a game.
  709.  
  710. Arguments:
  711. strategy -- a function to deploy ants to places
  712. beehive -- a Hive full of bees
  713. ant_types -- a list of ant constructors
  714. create_places -- a function that creates the set of places
  715. dimensions -- a pair containing the dimensions of the game layout
  716. """
  717. self.time = 0
  718. self.food = food
  719. self.strategy = strategy
  720. self.beehive = beehive
  721. self.ant_types = OrderedDict((a.name, a) for a in ant_types)
  722. self.dimensions = dimensions
  723. self.active_bees = []
  724. self.configure(beehive, create_places)
  725.  
  726. def configure(self, beehive, create_places):
  727. """Configure the places in the colony."""
  728. self.base = AntHomeBase('Ant Home Base')
  729. self.places = OrderedDict()
  730. self.bee_entrances = []
  731. def register_place(place, is_bee_entrance):
  732. self.places[place.name] = place
  733. if is_bee_entrance:
  734. place.entrance = beehive
  735. self.bee_entrances.append(place)
  736. register_place(self.beehive, False)
  737. create_places(self.base, register_place, self.dimensions[0], self.dimensions[1])
  738.  
  739. def simulate(self):
  740. """Simulate an attack on the ant colony (i.e., play the game)."""
  741. num_bees = len(self.bees)
  742. try:
  743. while True:
  744. self.strategy(self) # Ants deploy
  745. self.beehive.strategy(self) # Bees invade
  746. for ant in self.ants: # Ants take actions
  747. if ant.armor > 0:
  748. ant.action(self)
  749. for bee in self.active_bees[:]: # Bees take actions
  750. if bee.armor > 0:
  751. bee.action(self)
  752. if bee.armor <= 0:
  753. num_bees -= 1
  754. self.active_bees.remove(bee)
  755. if num_bees == 0:
  756. raise AntsWinException()
  757. self.time += 1
  758. except AntsWinException:
  759. print('All bees are vanquished. You win!')
  760. return True
  761. except BeesWinException:
  762. print('The ant queen has perished. Please try again.')
  763. return False
  764.  
  765. def deploy_ant(self, place_name, ant_type_name):
  766. """Place an ant if enough food is available.
  767.  
  768. This method is called by the current strategy to deploy ants.
  769. """
  770. constructor = self.ant_types[ant_type_name]
  771. if self.food < constructor.food_cost:
  772. print('Not enough food remains to place ' + ant_type_name)
  773. else:
  774. ant = constructor()
  775. self.places[place_name].add_insect(ant)
  776. self.food -= constructor.food_cost
  777. return ant
  778.  
  779. def remove_ant(self, place_name):
  780. """Remove an Ant from the game."""
  781. place = self.places[place_name]
  782. if place.ant is not None:
  783. place.remove_insect(place.ant)
  784.  
  785. @property
  786. def ants(self):
  787. return [p.ant for p in self.places.values() if p.ant is not None]
  788.  
  789. @property
  790. def bees(self):
  791. return [b for p in self.places.values() for b in p.bees]
  792.  
  793. @property
  794. def insects(self):
  795. return self.ants + self.bees
  796.  
  797. def __str__(self):
  798. status = ' (Food: {0}, Time: {1})'.format(self.food, self.time)
  799. return str([str(i) for i in self.ants + self.bees]) + status
  800.  
  801. class AntHomeBase(Place):
  802. """AntHomeBase at the end of the tunnel, where the queen resides."""
  803.  
  804. def add_insect(self, insect):
  805. """Add an Insect to this Place.
  806.  
  807. Can't actually add Ants to a AntHomeBase. However, if a Bee attempts to
  808. enter the AntHomeBase, a BeesWinException is raised, signaling the end
  809. of a game.
  810. """
  811. assert isinstance(insect, Bee), 'Cannot add {0} to AntHomeBase'
  812. raise BeesWinException()
  813.  
  814. def ants_win():
  815. """Signal that Ants win."""
  816. raise AntsWinException()
  817.  
  818. def bees_win():
  819. """Signal that Bees win."""
  820. raise BeesWinException()
  821.  
  822. def ant_types():
  823. """Return a list of all implemented Ant classes."""
  824. all_ant_types = []
  825. new_types = [Ant]
  826. while new_types:
  827. new_types = [t for c in new_types for t in c.__subclasses__()]
  828. all_ant_types.extend(new_types)
  829. return [t for t in all_ant_types if t.implemented]
  830.  
  831. class GameOverException(Exception):
  832. """Base game over Exception."""
  833. pass
  834.  
  835. class AntsWinException(GameOverException):
  836. """Exception to signal that the ants win."""
  837. pass
  838.  
  839. class BeesWinException(GameOverException):
  840. """Exception to signal that the bees win."""
  841. pass
  842.  
  843. def interactive_strategy(gamestate):
  844. """A strategy that starts an interactive session and lets the user make
  845. changes to the gamestate.
  846.  
  847. For example, one might deploy a ThrowerAnt to the first tunnel by invoking
  848. gamestate.deploy_ant('tunnel_0_0', 'Thrower')
  849. """
  850. print('gamestate: ' + str(gamestate))
  851. msg = '<Control>-D (<Control>-Z <Enter> on Windows) completes a turn.\n'
  852. interact(msg)
  853.  
  854. ###########
  855. # Layouts #
  856. ###########
  857.  
  858. def wet_layout(queen, register_place, tunnels=3, length=9, moat_frequency=3):
  859. """Register a mix of wet and and dry places."""
  860. for tunnel in range(tunnels):
  861. exit = queen
  862. for step in range(length):
  863. if moat_frequency != 0 and (step + 1) % moat_frequency == 0:
  864. exit = Water('water_{0}_{1}'.format(tunnel, step), exit)
  865. else:
  866. exit = Place('tunnel_{0}_{1}'.format(tunnel, step), exit)
  867. register_place(exit, step == length - 1)
  868.  
  869. def dry_layout(queen, register_place, tunnels=3, length=9):
  870. """Register dry tunnels."""
  871. wet_layout(queen, register_place, tunnels, length, 0)
  872.  
  873.  
  874. #################
  875. # Assault Plans #
  876. #################
  877.  
  878. class AssaultPlan(dict):
  879. """The Bees' plan of attack for the colony. Attacks come in timed waves.
  880.  
  881. An AssaultPlan is a dictionary from times (int) to waves (list of Bees).
  882.  
  883. >>> AssaultPlan().add_wave(4, 2)
  884. {4: [Bee(3, None), Bee(3, None)]}
  885. """
  886.  
  887. def add_wave(self, bee_type, bee_armor, time, count):
  888. """Add a wave at time with count Bees that have the specified armor."""
  889. bees = [bee_type(bee_armor) for _ in range(count)]
  890. self.setdefault(time, []).extend(bees)
  891. return self
  892.  
  893. @property
  894. def all_bees(self):
  895. """Place all Bees in the beehive and return the list of Bees."""
  896. return [bee for wave in self.values() for bee in wave]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement