ants_grader.py (plain text)


"""Unit tests for Ants Vs. SomeBees."""

import unittest
import doctest
import os
import sys
import imp
from ucb import main
import ants
import autograder

__version__ = '1.4'


class AntTest(unittest.TestCase):

    def setUp(self):
        hive, layout = ants.Hive(ants.make_test_assault_plan()), ants.test_layout
        self.colony = ants.AntColony(None, hive, ants.ant_types(), layout)


class TestProblem2(AntTest):

    def test_food_costs(self):
        error_msg = "Wrong food_cost for ant class"
        self.assertEqual(2, ants.HarvesterAnt.food_cost, error_msg)
        self.assertEqual(4, ants.ThrowerAnt.food_cost, error_msg)

    def test_harvester(self):
        error_msg = "HarvesterAnt did not add one food"
        old_food = self.colony.food
        ants.HarvesterAnt().action(self.colony)
        self.assertEqual(old_food + 1, self.colony.food, error_msg)


class TestProblem3(AntTest):

    def test_connectedness(self):
        error_msg = "Entrances not properly initialized"
        for entrance in self.colony.bee_entrances:
            place = entrance
            while place:
                self.assertIsNotNone(place.entrance, msg=error_msg)
                place = place.exit

    def test_exits_and_entrances_are_different(self):
        for place in self.colony.places.values():
            self.assertIsNot(place, place.exit,
                             '{0} is its own exit'.format(place))
            self.assertIsNot(place, place.entrance,
                             '{0} is its own entrance'.format(place))
            if place.exit and place.entrance:
                self.assertIsNot(place.exit, place.entrance,
                                 '{0} entrance is its exit'.format(place))


class TestProblemA4(AntTest):

    def test_water_deadliness(self):
        error_msg = "Water does not kill non-watersafe Insects"
        test_ant = ants.HarvesterAnt()
        test_water = ants.Water("water_TestProblemA4_0")
        test_water.add_insect(test_ant)
        self.assertIsNot(test_ant, test_water.ant, msg=error_msg)
        self.assertEqual(0, test_ant.armor, msg=error_msg)

    def test_water_deadliness_with_big_soggy_bee(self):
        error_msg = "Water does not hurt all non-watersafe Insects"
        test_ants = [ants.Bee(1000000), ants.HarvesterAnt(), ants.Ant(),
                     ants.ThrowerAnt()]
        test_ants[0].watersafe = False # Make the Bee non-watersafe
        test_water = ants.Water("water_TestProblemA4_0")
        for test_ant in test_ants:
            test_water.add_insect(test_ant)
            self.assertIsNot(test_ant, test_water.ant, msg=error_msg)
            self.assertIs(0, test_ant.armor, msg=error_msg)

    def test_water_safety(self):
        error_msg = "Water kills watersafe Insects"
        test_bee = ants.Bee(1)
        test_water = ants.Water("water_testProblemA4_0")
        test_water.add_insect(test_bee)
        self.assertIn(test_bee, test_water.bees, msg=error_msg)

    def test_water_inheritance(self):
        error_msg = "It seems Water.add_insect is not using inheritance"
        old_add_insect = ants.Place.add_insect
        def new_add_insect(self, insect):
            raise NotImplementedError()
        ants.Place.add_insect = new_add_insect
        test_bee = ants.Bee(1)
        test_water = ants.Water("water_testProblemA4_0")
        failed = True
        try:
            test_water.add_insect(test_bee)
        except NotImplementedError:
            failed = False
        finally:
            ants.Place.add_insect = old_add_insect
        if failed:
            self.fail(msg=error_msg)

    def test_water_inheritance_ant(self):
        error_msg = "Make sure to place the ant before watering it"
        old_add_insect = ants.Place.add_insect
        def new_add_insect(self, insect):
            raise NotImplementedError()
        ants.Place.add_insect = new_add_insect
        test_ant = ants.HarvesterAnt()
        test_water = ants.Water("water_testProblemA4_0")
        failed = True
        try:
            test_water.add_insect(test_ant)
        except NotImplementedError:
            failed = False
        finally:
            ants.Place.add_insect = old_add_insect
        if failed:
            self.fail(msg=error_msg)


class TestProblemA5(AntTest):

    def test_fire_parameters(self):
        fire = ants.FireAnt()
        self.assertEqual(4, ants.FireAnt.food_cost, "FireAnt has wrong cost")
        self.assertEqual(1, fire.armor, "FireAnt has wrong armor value")

    def test_fire_damage(self):
        error_msg = "FireAnt does the wrong amount of damage"
        place = self.colony.places["tunnel_0_0"]
        bee = ants.Bee(5)
        place.add_insect(bee)
        place.add_insect(ants.FireAnt())
        bee.action(self.colony)
        self.assertEqual(2, bee.armor, error_msg)

    def test_fire_deadliness(self):
        error_msg = "FireAnt does not damage all Bees in its Place"
        test_place = self.colony.places["tunnel_0_0"]
        bee = ants.Bee(3)
        test_place.add_insect(bee)
        test_place.add_insect(ants.Bee(3))
        test_place.add_insect(ants.FireAnt())
        bee.action(self.colony)
        self.assertEqual(0, len(test_place.bees), error_msg)

    def test_fire_damage_is_instance_attribute(self):
        error_msg = "FireAnt damage is not looked up on the instance"
        place = self.colony.places["tunnel_0_0"]
        bee = ants.Bee(900)
        place.add_insect(bee)
        buffAnt = ants.FireAnt()
        buffAnt.damage = 500  # Feel the burn!
        place.add_insect(buffAnt)
        bee.action(self.colony)
        self.assertEqual(400, bee.armor, error_msg)

    def test_fireant_expiration(self):
        error_msg = "FireAnt should have, but did not expire"
        place = self.colony.places["tunnel_0_0"]
        bee = ants.Bee(1)
        place.add_insect(bee)
        ant = ants.FireAnt()
        place.add_insect(ant)
        bee.action(self.colony)
        self.assertEqual(ant.armor, 0, error_msg)


class TestProblemB4(AntTest):

    def test_nearest_bee(self):
        error_msg = "ThrowerAnt can't find the nearest bee"
        ant = ants.ThrowerAnt()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        near_bee = ants.Bee(2)
        self.colony.places["tunnel_0_3"].add_insect(near_bee)
        self.colony.places["tunnel_0_6"].add_insect(ants.Bee(2))
        hive = self.colony.hive
        self.assertIs(ant.nearest_bee(hive), near_bee, error_msg)
        ant.action(self.colony)
        self.assertEqual(1, near_bee.armor, error_msg)

    def test_nearest_bee_not_in_hive(self):
        error_msg = "ThrowerAnt hit a Bee in the Hive"
        ant = ants.ThrowerAnt()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        hive = self.colony.hive
        hive.add_insect(ants.Bee(2))
        self.assertIsNone(ant.nearest_bee(hive), error_msg)

    def test_melee(self):
        error_msg = "ThrowerAnt doesn't attack bees on its own square."
        ant = ants.ThrowerAnt()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        near_bee = ants.Bee(2)
        self.colony.places["tunnel_0_0"].add_insect(near_bee)
        self.assertIs(ant.nearest_bee(self.colony.hive), near_bee, error_msg)
        ant.action(self.colony)
        self.assertIs(1, near_bee.armor, error_msg)

    def test_random_shot(self):
        error_msg = "ThrowerAnt does not appear to choose random targets"
        ant = ants.ThrowerAnt()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        # Place two ultra-bees to test randomness.
        bee = ants.Bee(1001)
        self.colony.places["tunnel_0_3"].add_insect(bee)
        self.colony.places["tunnel_0_3"].add_insect(ants.Bee(1001))
        # Throw 1000 times. The first bee should take ~1000*1/2 = ~500 damage,
        # and have ~501 remaining.
        for _ in range(1000):
            ant.action(self.colony)
        # Test if damage to bee 1 is within 6 standard deviations (~95 damage)
        # If bees are chosen uniformly, this is true 99.9999998% of the time.
        def dmg_within_tolerance():
            return abs(bee.armor-501) < 95
        self.assertIs(True, dmg_within_tolerance(), error_msg)


class TestProblemB5(AntTest):

    def test_thrower_parameters(self):
        short_t = ants.ShortThrower()
        long_t = ants.LongThrower()
        self.assertEqual(3, ants.ShortThrower.food_cost, "ShortThrower has wrong cost")
        self.assertEqual(3, ants.LongThrower.food_cost, "LongThrower has wrong cost")
        self.assertEqual(1, short_t.armor, "ShortThrower has wrong armor")
        self.assertEqual(1, long_t.armor, "LongThrower has wrong armor")

    def test_inheritance(self):
        """Tests to see if the Long and Short Throwers are actually using the
        inherited action from the ThrowerAnt.
        """
        old_thrower_action = ants.ThrowerAnt.action
        old_throw_at = ants.ThrowerAnt.throw_at
        def new_thrower_action(self, colony):
            raise NotImplementedError()
        def new_throw_at(self, target):
            raise NotImplementedError()

        failed_long = 0
        try: # Test action
            ants.ThrowerAnt.action = new_thrower_action
            test_long = ants.LongThrower()
            test_long.action(self.colony)
        except NotImplementedError:
            failed_long += 1
        finally:
            ants.ThrowerAnt.action = old_thrower_action

        try: # Test throw_at
            ants.ThrowerAnt.throw_at = new_throw_at
            test_long = ants.LongThrower()
            test_bee = ants.Bee(1)
            test_long.throw_at(test_bee)
        except NotImplementedError:
            failed_long += 1
        finally:
            ants.ThrowerAnt.throw_at = old_throw_at

        if failed_long < 2:
            self.fail(msg="LongThrower is not using inheritance")

        failed_short = 0
        try: # Test action
            ants.ThrowerAnt.action = new_thrower_action
            test_short = ants.ShortThrower()
            test_short.action(self.colony)
        except NotImplementedError:
            failed_short += 1
        finally:
            ants.ThrowerAnt.action = old_thrower_action

        try: # Test throw_at
            ants.ThrowerAnt.throw_at = new_throw_at
            test_short = ants.ShortThrower()
            test_bee = ants.Bee(1)
            test_short.throw_at(test_bee)
        except NotImplementedError:
            failed_short += 1
        finally:
            ants.ThrowerAnt.throw_at = old_throw_at

        if failed_short < 2:
            self.fail(msg="ShortThrower is not using inheritance")

    def test_long(self):
        error_msg = "LongThrower has the wrong range"
        ant = ants.LongThrower()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        out_of_range, in_range = ants.Bee(2), ants.Bee(2)
        self.colony.places["tunnel_0_3"].add_insect(out_of_range)
        self.colony.places["tunnel_0_4"].add_insect(in_range)
        ant.action(self.colony)
        self.assertEqual(in_range.armor, 1, error_msg)
        self.assertEqual(out_of_range.armor, 2, error_msg)

    def test_short(self):
        error_msg = "ShortThrower has the wrong range"
        ant = ants.ShortThrower()
        self.colony.places["tunnel_0_0"].add_insect(ant)
        out_of_range, in_range = ants.Bee(2), ants.Bee(2)
        self.colony.places["tunnel_0_3"].add_insect(out_of_range)
        self.colony.places["tunnel_0_2"].add_insect(in_range)
        ant.action(self.colony)
        self.assertEqual(in_range.armor, 1, error_msg)
        self.assertEqual(out_of_range.armor, 2, error_msg)

    def test_instance_range(self):
        error_msg = "Range is not looked up on the instance"
        #Buff ant range
        ant = ants.ShortThrower()
        ant.max_range = 10
        self.colony.places["tunnel_0_0"].add_insect(ant)
        #Place a bee out of normal range
        bee = ants.Bee(2)
        self.colony.places["tunnel_0_6"].add_insect(bee)
        ant.action(self.colony)
        self.assertIs(bee.armor, 1, error_msg)


class TestProblemA6(AntTest):

    def test_wall(self):
        error_msg = "WallAnt isn't parameterized quite right"
        self.assertEqual(4, ants.WallAnt().armor, error_msg)
        self.assertEqual(4, ants.WallAnt.food_cost, error_msg)


class TestProblemA7(AntTest):

    def test_ninja_parameters(self):
        ninja = ants.NinjaAnt()
        self.assertEqual(6, ants.NinjaAnt.food_cost, "NinjaAnt has wrong cost")
        self.assertEqual(1, ninja.armor, "NinjaAnt has wrong armor")

    def test_non_ninja_blocks(self):
        error_msg = "Non-NinjaAnt does not block bees"
        p0 = self.colony.places["tunnel_0_0"]
        p1 = self.colony.places["tunnel_0_1"]
        bee = ants.Bee(2)
        p1.add_insect(bee)
        p1.add_insect(ants.ThrowerAnt())
        bee.action(self.colony)
        self.assertIsNot(p0, bee.place, error_msg)

    def test_ninja_does_not_block(self):
        error_msg = "NinjaAnt blocks bees"
        p0 = self.colony.places["tunnel_0_0"]
        p1 = self.colony.places["tunnel_0_1"]
        bee = ants.Bee(2)
        p1.add_insect(bee)
        p1.add_insect(ants.NinjaAnt())
        bee.action(self.colony)
        self.assertIs(p0, bee.place, error_msg)

    def test_ninja_deadliness(self):
        error_msg = "NinjaAnt does not strike all bees in its place"
        test_place = self.colony.places["tunnel_0_0"]
        for _ in range(3):
            test_place.add_insect(ants.Bee(1))
        ninja = ants.NinjaAnt()
        test_place.add_insect(ninja)
        ninja.action(self.colony)
        self.assertEqual(0, len(test_place.bees), error_msg)

    def test_instance_damage(self):
        error_msg = "Ninja damage not looked up on the instance"
        place = self.colony.places["tunnel_0_0"]
        bee = ants.Bee(900)
        place.add_insect(bee)
        buffNinja = ants.NinjaAnt()
        buffNinja.damage = 500  # Sharpen the sword
        place.add_insect(buffNinja)
        buffNinja.action(self.colony)
        self.assertEqual(400, bee.armor, error_msg)


class TestProblemB6(AntTest):

    def test_scuba_parameters(self):
        scuba = ants.ScubaThrower()
        self.assertEqual(5, ants.ScubaThrower.food_cost, "ScubaThrower has wrong cost")
        self.assertEqual(1, scuba.armor, "ScubaThrower has wrong armor")

    def test_inheritance(self):
        """Tests to see if the ScubaThrower is actually using the inherited
        action from the ThrowerAnt.
        """
        old_thrower_action = ants.ThrowerAnt.action
        def new_thrower_action(self, colony):
            raise NotImplementedError()
        old_throw_at = ants.ThrowerAnt.throw_at
        def new_throw_at(self, target):
            raise NotImplementedError()

        failed_scuba = 0
        try:
            ants.ThrowerAnt.action = new_thrower_action
            test_scuba = ants.ScubaThrower()
            test_scuba.action(self.colony)
        except NotImplementedError:
            failed_scuba += 1
        finally:
            ants.ThrowerAnt.action = old_thrower_action

        try:
            ants.ThrowerAnt.throw_at = new_throw_at
            test_scuba = ants.ScubaThrower()
            test_bee = ants.Bee(1)
            test_scuba.throw_at(test_bee)
        except NotImplementedError:
            failed_scuba += 1
        finally:
            ants.ThrowerAnt.throw_at = old_throw_at

        if failed_scuba < 2:
            self.fail(msg="ScubaThrower is not using inheritance")

    def test_scuba(self):
        error_msg = "ScubaThrower sank"
        water = ants.Water("water")
        ant = ants.ScubaThrower()
        water.add_insect(ant)
        self.assertIs(water, ant.place, error_msg)
        self.assertEqual(1, ant.armor, error_msg)

    def test_scuba_on_land(self):
        place1 = self.colony.places["tunnel_0_0"]
        place2 = self.colony.places["tunnel_0_4"]
        ant = ants.ScubaThrower()
        bee = ants.Bee(3)
        place1.add_insect(ant)
        place2.add_insect(bee)
        ant.action(self.colony)
        self.assertEqual(2, bee.armor, "ScubaThrower doesn't throw on land")

    def test_scuba_in_water(self):
        water = ants.Water("water")
        water.entrance = self.colony.places["tunnel_0_1"]
        target = self.colony.places["tunnel_0_4"]
        ant = ants.ScubaThrower()
        bee = ants.Bee(3)
        water.add_insect(ant)
        target.add_insect(bee)
        ant.action(self.colony)
        self.assertIs(2, bee.armor, "ScubaThrower doesn't throw in water")


class TestProblemB7(AntTest):

    def test_hungry_parameters(self):
        hungry = ants.HungryAnt()
        self.assertEqual(4, ants.HungryAnt.food_cost, "HungryAnt has wrong cost")
        self.assertEqual(1, hungry.armor, "HungryAnt has wrong armor")

    def test_hungry_eats_and_digests(self):
        hungry = ants.HungryAnt()
        super_bee, super_pal = ants.Bee(1000), ants.Bee(1)
        place = self.colony.places["tunnel_0_0"]
        place.add_insect(hungry)
        place.add_insect(super_bee)
        hungry.action(self.colony)
        self.assertEqual(0, super_bee.armor, "HungryAnt didn't eat")
        place.add_insect(super_pal)
        for _ in range(3):
            hungry.action(self.colony)
        self.assertEqual(1, super_pal.armor, "HungryAnt didn't digest")
        hungry.action(self.colony)
        self.assertEqual(0, super_pal.armor, "HungryAnt didn't eat again")

    def test_hungry_waits(self):
        """If you get an IndexError (not an AssertionError) when running
        this test, it"s possible that your HungryAnt is trying to eat a
        bee when no bee is available.
        """
        hungry = ants.HungryAnt()
        place = self.colony.places["tunnel_0_0"]
        place.add_insect(hungry)
        # Wait a few turns before adding Bee
        for _ in range(5):
            hungry.action(self.colony)
        bee = ants.Bee(3)
        place.add_insect(bee)
        hungry.action(self.colony)
        self.assertIs(0, bee.armor, "HungryAnt didn't eat")


    def test_hungry_delay(self):
        # Add very hungry caterpi- um, ant
        very_hungry = ants.HungryAnt()
        very_hungry.time_to_digest = 0
        place = self.colony.places["tunnel_0_0"]
        place.add_insect(very_hungry)
        # Eat all the bees!
        for _ in range(100):
            place.add_insect(ants.Bee(3))
        for _ in range(100):
            very_hungry.action(self.colony)
        self.assertIs(0, len(place.bees),
                      "Digestion time not looked up on the instance")


class TestProblem8(AntTest):

    def setUp(self):
        AntTest.setUp(self)
        self.place = ants.Place("TestProblem8")
        self.bodyguard = ants.BodyguardAnt()
        self.bodyguard2 = ants.BodyguardAnt()
        self.test_ant = ants.Ant()
        self.test_ant2 = ants.Ant()
        self.harvester = ants.HarvesterAnt()

    def test_bodyguard_parameters(self):
        bodyguard = ants.BodyguardAnt()
        self.assertEqual(4, ants.BodyguardAnt.food_cost, "BodyguardAnt has wrong cost")
        self.assertEqual(2, bodyguard.armor, "BodyguardAnt has wrong armor")

    def test_bodyguardant_starts_empty(self):
        error_msg = "BodyguardAnt doesn't start off empty"
        self.assertIsNone(self.bodyguard.ant, error_msg)

    def test_contain_ant(self):
        error_msg = "BodyguardAnt.contain_ant doesn't properly contain ants"
        self.bodyguard.contain_ant(self.test_ant)
        self.assertIs(self.bodyguard.ant, self.test_ant, error_msg)

    def test_bodyguardant_is_container(self):
        error_msg = "BodyguardAnt isn't a container"
        self.assertTrue(self.bodyguard.container, error_msg)

    def test_ant_is_not_container(self):
        error_msg = "Normal Ants are containers"
        self.assertFalse(self.test_ant.container, error_msg)

    def test_can_contain1(self):
        error_msg = "can_contain returns False for container ants"
        self.assertTrue(self.bodyguard.can_contain(self.test_ant), error_msg)

    def test_can_contain2(self):
        error_msg = "can_contain returns True for non-container ants"
        self.assertFalse(self.test_ant.can_contain(self.test_ant2), error_msg)

    def test_can_contain3(self):
        error_msg = "can_contain lets container ants contain other containers"
        self.assertFalse(self.bodyguard.can_contain(self.bodyguard2), error_msg)

    def test_can_contain4(self):
        error_msg = "can_contain lets container ants contain multiple ants"
        self.bodyguard.contain_ant(self.test_ant)
        self.assertFalse(self.bodyguard.can_contain(self.test_ant2), error_msg)

    def test_modified_add_insect1(self):
        error_msg = "Place.add_insect doesn't place Ants on BodyguardAnts properly"
        self.place.add_insect(self.bodyguard)
        try:
            self.place.add_insect(self.test_ant)
        except:
            self.fail(error_msg)
        self.assertIs(self.bodyguard.ant, self.test_ant, error_msg)
        self.assertIs(self.place.ant, self.bodyguard, error_msg)

    def test_modified_add_insect2(self):
        error_msg = \
            "Place.add_insect doesn't place BodyguardAnts on Ants properly"
        self.place.add_insect(self.test_ant)
        try:
            self.place.add_insect(self.bodyguard)
        except:
            self.fail(error_msg)
        self.assertIs(self.bodyguard.ant, self.test_ant, error_msg)
        self.assertIs(self.place.ant, self.bodyguard, error_msg)

    def test_bodyguardant_perish(self):
        error_msg = \
            "BodyguardAnts aren't replaced with the contained Ant when perishing"
        self.place.add_insect(self.bodyguard)
        self.place.add_insect(self.test_ant)
        self.bodyguard.reduce_armor(self.bodyguard.armor)
        self.assertIs(self.place.ant, self.test_ant, error_msg)

    def test_bodyguardant_work(self):
        error_msg = "BodyguardAnts don't let the contained ant do work"
        food = self.colony.food
        self.bodyguard.contain_ant(self.harvester)
        self.bodyguard.action(self.colony)
        self.assertEqual(food+1, self.colony.food, error_msg)

    def test_thrower(self):
        error_msg = "ThrowerAnt can't throw from inside a bodyguard"
        ant = ants.ThrowerAnt()
        self.colony.places["tunnel_0_0"].add_insect(self.bodyguard)
        self.colony.places["tunnel_0_0"].add_insect(ant)
        bee = ants.Bee(2)
        self.colony.places["tunnel_0_3"].add_insect(bee)
        self.bodyguard.action(self.colony)
        self.assertEqual(1, bee.armor, error_msg)

    def test_remove_bodyguard(self):
        error_msg = 'Removing BodyguardAnt also removes containing ant'
        place = self.colony.places['tunnel_0_0']
        bodyguard = ants.BodyguardAnt()
        test_ant = ants.Ant(1)
        place.add_insect(bodyguard)
        place.add_insect(test_ant)
        self.colony.remove_ant('tunnel_0_0')
        self.assertIs(place.ant, test_ant, error_msg)

    def test_bodyguarded_ant_do_action(self):
        error_msg = "Bodyguarded ant does not perform its action"
        class TestAnt(ants.Ant):
            def action(self, colony):
                self.armor += 9000
        test_ant = TestAnt(1)
        place = self.colony.places['tunnel_0_0']
        bodyguard = ants.BodyguardAnt()
        place.add_insect(test_ant)
        place.add_insect(bodyguard)
        place.ant.action(self.colony)
        self.assertEqual(place.ant.ant.armor, 9001, error_msg)

    def test_modified_container(self):
        """Test to see if we can construct a container besides Bodyguard."""
        error_msg = "Container status should not be unique to Bodyguard."
        ant = ants.ThrowerAnt()
        ant.container = True
        ant.ant = None
        self.assertTrue(ant.can_contain(ants.ThrowerAnt()), error_msg)

    def test_modified_guard(self):
        error_msg = "A container should not contain another container."
        bodyguard = ants.BodyguardAnt()
        mod_guard = ants.BodyguardAnt()
        mod_guard.container = False
        self.assertTrue(bodyguard.can_contain(mod_guard), error_msg)


class TestProblem9(AntTest):
    @staticmethod
    def queen_layout(queen, register_place, steps=5):
        "Create a two-tunnel layout with water in the middle of 5 steps."
        for tunnel in range(2):
            exit = queen
            for step in range(steps):
                place = ants.Water if step == steps//2 else ants.Place
                exit = place('tunnel_{0}_{1}'.format(tunnel, step), exit)
                register_place(exit, step == steps-1)

    def setUp(self):
        imp.reload(ants)
        hive = ants.Hive(ants.make_test_assault_plan())
        layout = TestProblem9.queen_layout
        self.colony = ants.AntColony(None, hive, ants.ant_types(), layout)
        self.queen = ants.QueenAnt()
        self.imposter = ants.QueenAnt()

    def test_queen_place(self):
        colony_queen = ants.Place('Original Queen Location of the Colony')
        ant_queen = ants.Place('Place given to the QueenAnt')
        queen_place = ants.QueenPlace(colony_queen, ant_queen)
        colony_queen.bees = [ants.Bee(1, colony_queen) for _ in range(3)]
        ant_queen.bees = [ants.Bee(2, colony_queen) for _ in range(4)]
        self.assertEqual(7, len(queen_place.bees), 'QueenPlace has wrong bees')
        bee_armor = sum(bee.armor for bee in queen_place.bees)
        self.assertEqual(11, bee_armor, 'QueenPlace has wrong bees')

    def test_double(self):
        back = ants.ThrowerAnt()
        thrower_damage = ants.ThrowerAnt.damage
        fire_damage = ants.FireAnt.damage
        front = ants.FireAnt()
        side_back = ants.ThrowerAnt()
        side_front = ants.ThrowerAnt()
        armor, side_armor = 20, 10
        bee, side_bee = ants.Bee(armor), ants.Bee(side_armor)

        self.colony.places['tunnel_0_0'].add_insect(back)
        self.colony.places['tunnel_0_2'].add_insect(self.queen)
        self.colony.places['tunnel_0_4'].add_insect(bee)
        self.colony.places['tunnel_1_1'].add_insect(side_back)
        self.colony.places['tunnel_1_3'].add_insect(side_front)
        self.colony.places['tunnel_1_4'].add_insect(side_bee)

        # Simulate a battle in Tunnel 0 (contains Queen)
        back.action(self.colony)
        armor -= thrower_damage  # No doubling until queen's action
        self.assertEqual(armor, bee.armor, "Damage doubled too early")
        self.queen.action(self.colony)
        armor -= thrower_damage  # Queen should always deal normal damage
        self.assertEqual(armor, bee.armor, "Queen damage incorrect")
        bee.action(self.colony)  # Bee moves forward
        self.colony.places['tunnel_0_3'].add_insect(front)  # Fire ant added
        back.action(self.colony)
        armor -= 2 * thrower_damage  # Damage doubled in back
        self.assertEqual(armor, bee.armor, "Back damage incorrect")
        self.queen.action(self.colony)
        armor -= thrower_damage  # Queen should always deal normal damage
        self.assertEqual(armor, bee.armor, "Queen damage incorrect (2)")
        back.action(self.colony)
        armor -= 2 * thrower_damage  # Thrower damage still doubled
        self.assertEqual(armor, bee.armor, "Back damage incorrect (2)")
        bee.action(self.colony)
        armor -= 2 * fire_damage  # Fire damage doubled
        self.assertEqual(armor, bee.armor, "Fire damage incorrect")

        # Simulate a battle in Tunnel 1 (no Queen)
        self.assertEqual(side_armor, side_bee.armor, "Side bee took damage")
        side_back.action(self.colony)
        side_armor -= thrower_damage  # Ant in another tunnel: normal damage
        self.assertEqual(side_armor, side_bee.armor,
                         "Side back damage incorrect")
        side_front.action(self.colony)
        side_armor -= thrower_damage  # Ant in another tunnel: normal damage
        self.assertEqual(side_armor, side_bee.armor,
                         "Side front damage incorrect")

    def test_die(self):
        bee = ants.Bee(3)
        self.colony.places['tunnel_0_1'].add_insect(self.queen)
        self.colony.places['tunnel_0_2'].add_insect(bee)
        self.queen.action(self.colony)
        self.assertFalse(len(self.colony.queen.bees) > 0, 'Game ended')
        bee.action(self.colony)
        self.assertTrue(len(self.colony.queen.bees) > 0, 'Game not ended')

    def test_imposter(self):
        queen = self.queen
        imposter = self.imposter
        self.colony.places['tunnel_0_0'].add_insect(queen)
        self.colony.places['tunnel_0_1'].add_insect(imposter)
        queen.action(self.colony)
        imposter.action(self.colony)
        self.assertEqual(1, queen.armor, 'Long live the queen')
        self.assertEqual(0, imposter.armor, 'Imposters must die')

    def test_bodyguard(self):
        bee = ants.Bee(3)
        guard = ants.BodyguardAnt()
        guard.damage, doubled = 5, 10
        self.colony.places['tunnel_0_1'].add_insect(self.queen)
        self.colony.places['tunnel_0_1'].add_insect(guard)
        self.colony.places['tunnel_0_2'].add_insect(bee)
        self.queen.action(self.colony)
        self.assertEqual(guard.damage, doubled, 'Bodyguard damage incorrect')
        self.assertFalse(len(self.colony.queen.bees) > 0, 'Game ended')
        bee.action(self.colony)
        self.assertTrue(len(self.colony.queen.bees) > 0, 'Game not ended')

    def test_remove(self):
        queen = self.queen
        imposter = self.imposter
        p0 = self.colony.places['tunnel_0_0']
        p1 = self.colony.places['tunnel_0_1']
        p0.add_insect(queen)
        p1.add_insect(imposter)
        p0.remove_insect(queen)
        p1.remove_insect(imposter)
        self.assertIs(queen, p0.ant, 'Queen removed')
        self.assertIsNone(p1.ant, 'Imposter not removed')
        queen.action(self.colony)

    def test_die_the_old_fashioned_way(self):
        bee = ants.Bee(3)
        queen = self.queen
        # The bee has an uninterrupted path to the heart of the colony
        self.colony.places['tunnel_0_1'].add_insect(bee)
        self.colony.places['tunnel_0_2'].add_insect(queen)
        queen.action(self.colony)
        bee.action(self.colony)
        self.assertFalse(len(self.colony.queen.bees) > 0, 'Game ended')
        queen.action(self.colony)
        bee.action(self.colony)
        self.assertTrue(len(self.colony.queen.bees) > 0, 'Game not ended')

    def test_double_continuous(self):
        """This test makes the queen buff one ant, then the other, to see
        if the queen will continually buff newly added ants.
        """
        self.colony.places['tunnel_0_0'].add_insect(ants.ThrowerAnt())
        self.colony.places['tunnel_0_2'].add_insect(self.queen)
        self.queen.action(self.colony)
        # Add ant and buff
        ant = ants.ThrowerAnt()
        self.colony.places['tunnel_0_1'].add_insect(ant)
        self.queen.action(self.colony)
        # Attack a bee
        bee = ants.Bee(3)
        self.colony.places['tunnel_0_4'].add_insect(bee)
        ant.action(self.colony)
        self.assertEqual(1, bee.armor, "Queen does not buff new ants")


class TestProblemEC(AntTest):

    def test_status_parameters(self):
        slow = ants.SlowThrower()
        stun = ants.StunThrower()
        self.assertEqual(4, ants.SlowThrower.food_cost, "SlowThrower has wrong cost")
        self.assertEqual(6, ants.StunThrower.food_cost, "StunThrower has wrong cost")
        self.assertEqual(1, slow.armor, "SlowThrower has wrong armor")
        self.assertEqual(1, stun.armor, "StunThrower has wrong armor")

    def test_slow(self):
        error_msg = "SlowThrower doesn't cause slowness on odd turns."
        slow = ants.SlowThrower()
        bee = ants.Bee(3)
        self.colony.places["tunnel_0_0"].add_insect(slow)
        self.colony.places["tunnel_0_4"].add_insect(bee)
        slow.action(self.colony)
        self.colony.time = 1
        bee.action(self.colony)
        self.assertEqual("tunnel_0_4", bee.place.name, error_msg)
        self.colony.time += 1
        bee.action(self.colony)
        self.assertEqual("tunnel_0_3", bee.place.name, error_msg)
        for _ in range(3):
            self.colony.time += 1
            bee.action(self.colony)
        self.assertEqual("tunnel_0_1", bee.place.name, error_msg)

    def test_stun(self):
        error_msg = "StunThrower doesn't stun for exactly one turn."
        stun = ants.StunThrower()
        bee = ants.Bee(3)
        self.colony.places["tunnel_0_0"].add_insect(stun)
        self.colony.places["tunnel_0_4"].add_insect(bee)
        stun.action(self.colony)
        bee.action(self.colony)
        self.assertEqual("tunnel_0_4", bee.place.name, error_msg)
        bee.action(self.colony)
        self.assertEqual("tunnel_0_3", bee.place.name, error_msg)

    def test_effect_stack(self):
        stun = ants.StunThrower()
        bee = ants.Bee(3)
        stun_place = self.colony.places["tunnel_0_0"]
        bee_place = self.colony.places["tunnel_0_4"]
        stun_place.add_insect(stun)
        bee_place.add_insect(bee)
        for _ in range(4): # stun bee four times
            stun.action(self.colony)
        for _ in range(4):
            bee.action(self.colony)
            self.assertEqual("tunnel_0_4", bee.place.name,
                             "Status effects do not stack")

    def test_multiple_stuns(self):
        error_msg = "2 StunThrowers stunning 2 Bees doesn't stun each Bee for exactly one turn."
        stun1 = ants.StunThrower()
        stun2 = ants.StunThrower()
        bee1 = ants.Bee(3)
        bee2 = ants.Bee(3)

        self.colony.places["tunnel_0_0"].add_insect(stun1)
        self.colony.places["tunnel_0_1"].add_insect(bee1)
        self.colony.places["tunnel_0_2"].add_insect(stun2)
        self.colony.places["tunnel_0_3"].add_insect(bee2)

        stun1.action(self.colony)
        stun2.action(self.colony)
        bee1.action(self.colony)
        bee2.action(self.colony)

        self.assertEqual("tunnel_0_1", bee1.place.name, error_msg)
        self.assertEqual("tunnel_0_3", bee2.place.name, error_msg)

        bee1.action(self.colony)
        bee2.action(self.colony)

        self.assertEqual("tunnel_0_0", bee1.place.name, error_msg)
        self.assertEqual("tunnel_0_2", bee2.place.name, error_msg)

    def test_long_effect_stack(self):
        stun = ants.StunThrower()
        slow = ants.SlowThrower()
        bee = ants.Bee(3)
        self.colony.places["tunnel_0_0"].add_insect(stun)
        self.colony.places["tunnel_0_1"].add_insect(slow)
        self.colony.places["tunnel_0_4"].add_insect(bee)
        for _ in range(3): # slow bee three times
            slow.action(self.colony)
        stun.action(self.colony) # stun bee once

        self.colony.time = 0
        bee.action(self.colony) # stunned
        self.assertEqual("tunnel_0_4", bee.place.name)

        self.colony.time = 1
        bee.action(self.colony) # slowed thrice
        self.assertEqual("tunnel_0_4", bee.place.name)

        self.colony.time = 2
        bee.action(self.colony) # slowed thrice
        self.assertEqual("tunnel_0_3", bee.place.name)

        self.colony.time = 3
        bee.action(self.colony) # slowed thrice
        self.assertEqual("tunnel_0_3", bee.place.name)

        self.colony.time = 4
        bee.action(self.colony) # slowed twice
        self.assertEqual("tunnel_0_2", bee.place.name)

        self.colony.time = 5
        bee.action(self.colony) # slowed twice
        self.assertEqual("tunnel_0_2", bee.place.name)

        self.colony.time = 6
        bee.action(self.colony) # slowed once
        self.assertEqual("tunnel_0_1", bee.place.name)

        self.colony.time = 7
        bee.action(self.colony) # no effects
        self.assertEqual(0, slow.armor)


project_info = {
    'name': 'Project 3: Ants',
    'remote_index': 'http://inst.eecs.berkeley.edu/~cs61a/fa13/proj/ants/',
    'autograder_files': [
        'ants_grader.py',
    ],
    'version': __version__,
}

@main
def main(*args):
    import argparse
    parser = argparse.ArgumentParser(description="Run Ants Tests")
    parser.add_argument("--verbose", "-v", action="store_true")
    parser.add_argument("--question", '-q')
    args = parser.parse_args()

    autograder.check_for_updates(
        project_info['remote_index'],
        project_info['autograder_files'],
        project_info['version'],
    )

    doctest.testmod(ants, verbose=args.verbose)

    question = args.question
    if question:
        question = question.upper()
        test_name = 'TestProblem' + question
        if test_name in globals():
            test = globals()[test_name]
            suite = unittest.makeSuite(test)
            runner = unittest.TextTestRunner()
        else:
            print('Question "{0}" not recognized.'.format(args.question),
                  'Try one of the following instead:')
            print('  2  3  A4  A5  B4  B5  A6  A7  B6  B7  8  9  EC')
            return

    stdout = sys.stdout # suppressing print statements from ants.py
    with open(os.devnull, "w") as sys.stdout:
        verbosity = 2 if args.verbose else 1
        if question:
            runner.run(suite)
        else:
            tests = unittest.main(exit=False, verbosity=verbosity)
    sys.stdout = stdout