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
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()
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)