def __init__(self, *args):
        super(Wife, self).__init__(*args)
        self.name = "Wife Elsa"
        self.location = SHACK
        #self.gold = 0
        #self.bank = 0
        #self.thirst = 0
        #self.fatigue = 0

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(DoHouseWork(),GlobalWifeState(),None)
    def __init__(self, *args):
        super(Miner, self).__init__(*args)
        self.name = "Miner Bob"
        self.location = SHACK
        self.gold = 0
        self.bank = 0
        self.thirst = 0
        self.fatigue = 0

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(DigInMine(),GlobalMinerState(),None)
class Wife(BaseEntity):
    """Wife Elsa, scourge of the goats.
    
    Note: The constructor doesn't take any actual args, but this syntax is
    needed to call the __init__ method of the superclass. I'm not sure that
    we need to do so here, but it will be a useful reminder for later.    
    """    

    def __init__(self, *args):
        super(Wife, self).__init__(*args)
        self.name = "Wife Elsa"
        self.location = SHACK
        #self.gold = 0
        #self.bank = 0
        #self.thirst = 0
        #self.fatigue = 0

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(DoHouseWork(),GlobalWifeState(),None)

    def update(self):
        """Updates the FSM logic, and nothing else."""
        self.fsm.update()

    def receive_msg(self,message):
        # Let the FSM handle any messages
        self.fsm.handle_msg(message)

    def change_location(self,newlocation):
        self.location = newlocation
    def __init__(self, *args):
        self.wall = args[2]
        args = (args[0], args[1])
        super(Attacker, self).__init__(*args)
        self.name = "Attacker"
        self.current_ladder = None
        self.current_height = 0

        print("%s : Ready for battle!" % self.name)

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(AttackerWait(), GlobalAttackerState(), None)
    def __init__(self, *args):
        self.wall = args[2]
        args = (args[0], args[1])
        super(Sentry, self).__init__(*args)
        self.name = "Sentry"

        self.location = 0
        self.fatigue = 0

        # Keep track of current direction (for PATROL)
        # This should be either +1 or -1
        self.direction = 1

        # Queue to keep track of what ladders to knock down
        self.task_queue = []
        self.current_task = None

        print("%s : Ready for battle!" % self.name)

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(SentryPatrol(), GlobalSentryState(), None)
class Attacker(BaseEntity):
    """Comment this class before they breach the walls!!!
    """

    def __init__(self, *args):
        self.wall = args[2]
        args = (args[0], args[1])
        super(Attacker, self).__init__(*args)
        self.name = "Attacker"
        self.current_ladder = None
        self.current_height = 0

        print("%s : Ready for battle!" % self.name)

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(AttackerWait(), GlobalAttackerState(), None)

    def update(self):
        self.fsm.update()

    def receive_msg(self, message):
        # Let the FSM handle any messages
        self.fsm.handle_msg(message)

    def start_climb(self, height=1):
        if self.wall.is_ladder(self.current_ladder):
            self.current_height = 1
            print("%s : Started climbing at space %d." % (self.name, self.current_ladder))
            return True
        else:
            return False

    def climb(self, rungs=1):
        if self.wall.is_ladder(self.current_ladder):
            self.current_height += rungs
            print("%s : Climbing at space %d; height now %d." % (self.name, self.current_ladder, self.current_height))
            return True
        else:
            return False

    def fall_down(self):
        print("%s : Fell from ladder at space %d." % (self.name, self.current_ladder))
        self.current_ladder = None
        self.current_height = 0
class Sentry(BaseEntity):
    """Sentry entity. Comment me!

    """

    # Game behaviour constants
    FATIGUE_LIGHT = 5
    FATIGUE_HEAVY = 20
    FATIGUE_IMMOBILE = 30
    FATIGUE_MOVE_COST = 3
    FATIGUE_KNOCKDOWN_COST = 5
    FATIGUE_RECOVERY = 7
    # TODO: There were inconsistencies in the specification for Patrol
    # regarding what distance the ladders could be detected at. These are
    # my best guesses, but the code should be checked.
    SIGHT_PATROL = 3
    SIGHT_REST = 2

    def __init__(self, *args):
        self.wall = args[2]
        args = (args[0], args[1])
        super(Sentry, self).__init__(*args)
        self.name = "Sentry"

        self.location = 0
        self.fatigue = 0

        # Keep track of current direction (for PATROL)
        # This should be either +1 or -1
        self.direction = 1

        # Queue to keep track of what ladders to knock down
        self.task_queue = []
        self.current_task = None

        print("%s : Ready for battle!" % self.name)

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(SentryPatrol(), GlobalSentryState(), None)


    def update(self):
        """Update the sentry's FSM logic."""
        self.fsm.update()

    def receive_msg(self, message):
        # Let the FSM handle any messages
        self.fsm.handle_msg(message)

    def move(self, spaces, direction=None):
        """Move the sentry along the wall.

        The sentry will attempt to move the given number of spaces. If the
        direction is not given, continue in current direction.

        Note: Even though the official requirements say that the sentry can
        move at most one space, this function can handle multiple spaces.
        """
        # If no direction given, use our current one.
        if direction is None:
            direction = self.direction

        new_location = self.location + spaces * direction
        new_location = min(max(0, new_location), WALL_MAX)

        cost = abs(self.location - new_location) * Sentry.FATIGUE_MOVE_COST
        self.location = new_location
        print("%s : Now at space %d." % (self.name, self.location))
        self.change_fatigue(cost)

    def about_face(self):
        """The sentry turns to face the opposite direction."""
        self.direction *= -1
        print("%s : About face! Now facing %d" % (self.name, self.direction))

    def change_fatigue(self, amount):
        self.fatigue += amount
        if self.fatigue < 0:
            self.fatigue = 0

    def get_task_from_queue(self):
        """Finds the first vaild item on the task_queue (which ladder needs
        to be knocked down), and sets the current_task."""
        try:
            space = self.task_queue.pop(0)
            print("%s : There's a ladder at space %d." % (self.name, space))
        except IndexError:
            self.current_task = None
            return

        if self.wall.is_ladder(space):
            self.current_task = space
        else:
            self.current_task = None

    def add_task_to_queue(self, space, check_duplicates=False):
        """Adds a new task to the end of our queue.
        Note: Does not check for duplicate tasks unless explicitly told to.
        """
        if check_duplicates is True:
            if space in self.task_queue:
                return False
        self.task_queue.append(space)
        return True

    def knockdown_ladder(self):
        # In order to check for errors, LADDER_DISPLACED message will be sent
        # by the wall, which then notifies attackers and scores point.
        if self.wall.knockdown_ladder(self.location):
            print("%s : Ladder down, huzzah!" % self.name)
            self.change_fatigue(Sentry.FATIGUE_KNOCKDOWN_COST)
            self.get_task_from_queue()
    # Yellow (ARRIVE, with low hesitance set by FSM later)
    x_new, y_new = obj[4].pos[0], obj[4].pos[1]
    obj[1].goal = obj[4]
    obj[1].hesitance = 0.5

    # Green (No hesitance, so FSM will use SEEK later)
    x_new, y_new = obj[5].pos[0], obj[5].pos[1]
    obj[2].goal = obj[5]

    # Target sprite groups for each vehicle
    for i in range(numveh):
        obj[i].tgroup = pygame.sprite.GroupSingle(obj[i + numveh].sprite)

    # Give each vehicle its own FSM
    for i in range(numveh):
        fsm = StateMachine(obj[i])
        fsm.set_state(InitialState())
        obj[i].fsm = fsm

    # Used by pygame for collision detection
    COLLIDE_FUNCTION = pygame.sprite.collide_circle

    ### Main loop ###
    ticks = TARGET_FREQ // 2
    while 1:
        for event in pygame.event.get():
            if event.type in [QUIT, MOUSEBUTTONDOWN]:
                pygame.quit()
                sys.exit()

        # FSM Updates
class Miner(BaseEntity):
    """Miner Bob.
    
    Note: The constructor doesn't take any actual args, but this syntax is
    needed to call the __init__ method of the superclass. I'm not sure that
    we need to do so here, but it will be a useful reminder for later.    
    """

    def __init__(self, *args):
        super(Miner, self).__init__(*args)
        self.name = "Miner Bob"
        self.location = SHACK
        self.gold = 0
        self.bank = 0
        self.thirst = 0
        self.fatigue = 0

        # Set up the FSM for this entity
        self.fsm = StateMachine(self)
        self.fsm.set_state(DigInMine(),GlobalMinerState(),None)
        #self.fsm.change_state(DigInMine())

    def update(self):
        """Increases thirst and updates the FSM logic."""
        self.thirst += 1
        self.fsm.update()

    def receive_msg(self,message):
        # print("%s: Got me a message of type %d!" % (self.name,msg_type))
        # Let the FSM handle any messages
        self.fsm.handle_msg(message)

    def change_location(self,newlocation):
        """Move to another location
        
        Location constants are enumerated in gamedata.py
        """
        self.location = newlocation

    def change_gold(self,amount):
        """Add/subtract the amount of gold currently carried
        
        Parameters
        ----------
        amount: int
            Amount of gold to add (or subtract, if negative)
        """
        self.gold += amount
        #print("[[%s]] : Now carrying %d gold nuggets." % (self.name, self.gold))

    def pockets_full(self):
        """Queries whether this entity is carrying enough gold."""
        return (self.gold >= 3)

    def add_fatigue(self,amount=1):
        """Increases the current fatigue of this entity."""
        self.fatigue += amount
        #print("[[%s]] : Now at fatigue level %d." % (self.name,self.fatigue))

    def remove_fatigue(self,amount=1):
        """Remove fatigue from this entity, but not below zero."""
        self.fatigue -= amount
        if self.fatigue < 0:
            self.fatigue = 0

    def is_thirsty(self):
        """Queries if this entity has too much current thirst."""
        return (self.thirst > 7)

    def remove_thirst(self,amount):
        """Remove thirst from this entity, but not below zero."""
        self.thirst -= amount
        if self.thirst < 0:
            self.thirst = 0

    def work_done(self):
        """Returns True if more than 10 gold in the bank.
        
        Note
        ----
        Fix this! Once there is 10 gold or more in the bank, the Miner
        will go home after each bank deposit. We don't want that.
        """              
        return (self.bank >= 10)