def __init__(self, template_string):
     """
 :param str template_string: string containing template variables
     and template escape statements to execute
 """
     self._extractor = LineExtractor(template_string)
     self._message = StatusMessage(self._extractor)
     self._executor = Executor()
     self._expander = Expander(self._executor, self._message)
     self._command = None  # Command being processed
     self._define_variable_statements = []
예제 #2
0
 def check_encounter(self, hunter, hazards=None):
     """
     Determines whether the hunter has stepped into a cave containing a bat colony.  The bat colony will lift the
     hunter and deposit him/her into another cave of the colony's choosing (not necessarily an adjoining cave since
     bats fly).  Since this is an equivalent to the hunter entering a new cave, we call that method on the hunter
     :param hunter: the hunter object
     :param hazards: listing of game hazards (needed here)
     :return: listing of status messages indicating the confrontation with the bats and the outcome of being
     ferried to another cave.
     """
     messages = []
     if hunter.cave.id == self.cave.id:
         hunter_cave_id_options = [
             item for item in list(range(1, 21)) if item != self.cave.id
         ]
         new_cave_id = random.choice(hunter_cave_id_options)
         messages.extend([
             StatusMessage(
                 'INFO', self.hazard_type,
                 "You've stumbled into a bat colony.  "
                 "Some of the bats are carrying you into another cave!")
         ])
         updated_status, _ = hunter.enter(new_cave_id,
                                          hazards,
                                          via_bat=True)
         messages.extend(updated_status)
     return messages
예제 #3
0
 def __init__(self, cavern_system, cave_id):
     super().__init__(cavern_system, cave_id)
     self.hazard_type = 'BAT_COLONY'
     self.hazard_perimeter = Hazard_Perimeter([
         StatusMessage('WARNING', self.hazard_type,
                       "You hear the flapping of wings")
     ], self.cave.neighboring_caves)
예제 #4
0
 def killed(self):
     """
     Kills the hunter.  That unfortunate fact is conveyed in the status message returned.
     :return: list of a single status message indicating the demise of the hunter.
     """
     self.alive = False
     return [
         StatusMessage('TERMINAL', 'GENERAL', "You've been killed.  Sorry.")
     ]
예제 #5
0
 def killed(self):
     """
     Indicates whether the Wumpus has been killed.
     :return: list containing the status message that the Wumpus has been slain.
     """
     self.alive = False
     return [
         StatusMessage('TERMINAL', self.hazard_type,
                       "You have slain the wumpus!")
     ]
class TestStatusMessage(unittest.TestCase):
    def setUp(self):
        self.extractor = LineExtractor("")
        self.message = StatusMessage(self.extractor)

    def testError(self):
        if IGNORE_TEST:
            return
        with self.assertRaises(ValueError):
            self.message.error("")

    def testWarning(self):
        if IGNORE_TEST:
            return
        with warnings.catch_warnings(record=True) as w:
            # Cause all warnings to always be triggered.
            self.message.warning("")
            # Verify some things
            assert len(w) == 1
예제 #7
0
 def awakened(self):
     """
     Indicates that the Wumpus has been awakened.
     :return: list containing the status message that the Wumpus is awake and moving.
     """
     self.asleep = False
     return [
         StatusMessage('INFO', self.hazard_type,
                       "Wumpus is awake and stiring!")
     ]
예제 #8
0
 def start_up(self, hazards):
     """
     Only called when the game is being initialized.  The hunter will not be placed with any hazards to begin with
     (that wouldn't be sporting) but the hunter may be close to one or more hazards in nearby caves.  The initial
     cavern map will show the cave the hunter starts in along with the three adjoining caves.
     :param hazards: list of game hazards
     :return: list of status messages
     """
     messages = [
         StatusMessage('INFO', 'GENERAL',
                       f"You are starting in cave {self.cave.id}")
     ]
     warnings = self.check_for_hazards(hazards)
     self.notebook.note_position(self.cave, warnings)
     messages.extend(warnings)
     return messages
예제 #9
0
 def from_json(cavern_system, json_array):
     """
     Restores the notebook game state from a json array of mapped site json objects and the cavern system
     :param cavern_system: layout of cavern system
     :param json_array: jsonified array containing the mapped site named tuples that make of the cavern map
     :return: the reconstituted cavern map.
     """
     cavern_map = []
     for json_object in json_array:
         cave = cavern_system.get_cave(json_object['cave_id'])
         status_messages = []
         for warning in json_object['warnings']:
             status_messages.append(StatusMessage.from_json(warning))
         mapped_site = Mapped_Site(cave, status_messages)
         cavern_map.append(mapped_site)
     return cavern_map
예제 #10
0
 def establish_hazard_perimeter(self):
     """
     The Wumpus hazard permeter extends as far as two caves removed from the Wumpus' location.
     """
     surrounding_caves = [
         self.cavern_system.get_cave(cave_id)
         for cave_id in self.cave.neighboring_caves
     ]
     surrounding_cave_ids = list(
         itertools.chain(
             *[cave.neighboring_caves for cave in surrounding_caves]))
     surrounding_cave_ids.extend(self.cave.neighboring_caves)
     self.hazard_perimeter = Hazard_Perimeter([
         StatusMessage('WARNING', self.hazard_type,
                       "You smell a wumpus (ick!)")
     ], set(surrounding_cave_ids))
예제 #11
0
 def check_encounter(self, hunter, hazards=None):
     """
     Determines whether the hunter has succumbed to this deadly hazard.  Kill of the hunter and report such if that
     is the case.
     :param hunter: the hunter object
     :param hazards: lising of game hazards (not relevant here)
     :return: list of status messages possibly noting the demise of the hunter.
     """
     messages = []
     if hunter.cave.id == self.cave.id:
         messages.extend([
             StatusMessage('TERMINAL', self.hazard_type,
                           "You fell into a bottomless pit!")
         ])
         messages.extend(hunter.killed())
     return messages
예제 #12
0
 def check_encounter(self, hunter, hazards=None):
     """
     If the hunter stumbles into the cave containing the Wumpus, the Wumpus will awake if not already awake, and
     eat the hunter.
     :param hunter: the hunter object
     :param hazards: list of game hazards - not relevant for a Wumpus encounter
     :return: the status messages indicating the hunter's sad end.
     """
     messages = []
     if hunter.cave.id == self.cave.id:
         if self.asleep:
             messages.extend(self.awakened())
         messages.extend([
             StatusMessage(
                 'TERMINAL', self.hazard_type,
                 "Uh oh!  You and the wumpus are occupying the same cave now."
             )
         ])
         messages.extend(hunter.killed())
     return messages
예제 #13
0
 def __init__(self, cavern_system, cave_id):
     super().__init__(cavern_system, cave_id)
     self.hazard_type = 'BOTTOMLESS_PIT'
     self.hazard_perimeter = Hazard_Perimeter(
         [StatusMessage('WARNING', self.hazard_type, "You feel a draft")],
         self.cave.neighboring_caves)
class TemplateProcessor(object):
    """
  This class processes an Antimony model written using template variable substitutions.
  See the project README for syntax details.
  """
    def __init__(self, template_string):
        """
    :param str template_string: string containing template variables
        and template escape statements to execute
    """
        self._extractor = LineExtractor(template_string)
        self._message = StatusMessage(self._extractor)
        self._executor = Executor()
        self._expander = Expander(self._executor, self._message)
        self._command = None  # Command being processed
        self._define_variable_statements = []

    @classmethod
    def processFile(cls, inpath, outpath):
        """
    Processes template strings in a file.
    :param str inpath: path to the file containing the templated model
    :param str outpath: path to the file where the flattened model is placed
    """
        template = ''
        with open(inpath, 'r') as infile:
            for line in infile:
                template += "\n" + line
        processor = cls(template)
        expansion = processor.do()
        with open(outpath, 'w') as outfile:
            outfile.write(expansion)

    @staticmethod
    def _makeComment(line):
        return "%s%s" % (COMMENT_STG, line)

    def _processCommand(self):
        """
    Handles command processing, either the current line
    is a command or in the midst of processing a paired command.
    :param list-of-str expansion:
    :return bool: True if processed line
    """
        line = self._extractor.getCurrentLine()
        line_type = self._extractor.getCurrentLineType()
        is_processed = False
        # Current line is a command
        if line_type == LINE_COMMAND:
            is_processed = True
            # Check for nested commands
            if self._command is not None:
                new_command = Command(line)
                # Is this a paired command?
                if new_command.getCommandVerb()  \
                    == self._command.getCommandVerb():
                    if new_command.isEnd():
                        pass
                    else:
                        self._message.error("Cannot nest commands")
            # Valid placement for a command.
            self._command = Command(line)
            # DefineVariables Command
            if self._command.isDefineVariables():
                if self._command.isBegin():
                    self._define_variables_statements = []
                elif self._command.isEnd():
                    try:
                        program = '\n'.join(self._define_variables_statements)
                        self._executor.doScript(program)
                    except Exception as err:
                        msg = "***Error %s executing in : \n%s"  \
                            % (str(err), program)
                        self._message.error(msg)
                    self._command = None
            # SetVersion command
            elif self._command.isSetVersion():
                version = self._command.getArguments()[0]
                if float(version) > float(VERSION):
                    self._message.error("Unsupported version %s" % version)
                self._command = None
            # Other commands
            else:
                self._message.error("Unknown command")
        # Process statements occurring within paired commands
        elif self._command is not None:
            is_processed = True
            if self._command.isDefineVariables() and self._command.isBegin():
                self._define_variables_statements.append(line)
            else:
                self._message.error("Invalid paired command.")
        return is_processed

    def do(self):
        """
    Processes the template string and returns the expanded lines for input
    to road runner.
    Phases
      1. Construct content lines (non-blank, not comments)
      2. Extract the template variable definitions
      3. Construct the substitution instances
      4. Process the lines with template variables
    State used:
      reads: _definitions
    :return str expanded_string:
    :raises ValueError: errors encountered in the template string
    """
        cls = TemplateProcessor
        expansion = []
        line, line_type = self._extractor.do()
        statements = []
        while line is not None:
            if self._processCommand():
                expansion.append(cls._makeComment(line))
            # No command being processed
            else:
                # Transparent line (comment)
                if line_type == LINE_TRAN:
                    expansion.append(line)
                elif line_type == LINE_NONE:
                    pass
                # Line to be substituted
                elif line_type == LINE_SUBS:
                    # Do the variable substitutions
                    try:
                        substitutions = self._expander.do(line)
                    except Exception as err:
                        msg = "Runtime error in expression"
                        self._message.error(msg)
                    if len(substitutions) > 1:
                        expansion.append(cls._makeComment(line))
                    expansion.extend(substitutions)
                else:
                    import pdb
                    pdb.set_trace()
                    raise RuntimeError("Unexepcted state")
            line, line_type = self._extractor.do()
        if self._command is not None:
            msg = "Still processing command %s at EOF" % str(self._command)
            self._message.error(msg)
        return "\n".join(expansion)
예제 #15
0
    def enter(self, cave_id, hazards, via_bat=False):
        """
        Hunter enters a cave.  It is possible that the hunter was moved by a bat if the hunter had stumbled into
        a bad colony.
        :param hazards: list of game hazards
        :param cave_id: id of cave into which hunter enters (or is dropped)
        :param via_bat: true if the hunter was dropped into the cave via a bat and false otherwise
        :return: a tuple containing the game state and errors if any.  The error is a server side validation of the
        choice of cave to enter.
        """
        status = []
        errors = []

        # The hunter may only enter a cave adjoining the one s/he came from unless transported via a bat.
        if cave_id in self.cave.neighboring_caves or via_bat:

            # Identify the new cave
            self.cave = self.cavern_system.get_cave(cave_id)

            # Provide informational messages appropriate to the circumstance.
            if via_bat:
                status.extend([
                    StatusMessage(
                        'INFO', 'BAT_COLONY',
                        f"You are being dropped into cave {cave_id}")
                ])
            else:
                status.extend([
                    StatusMessage('INFO', 'GENERAL',
                                  f"You are moving into cave {cave_id}")
                ])

            status.extend([StatusMessage('INFO', 'GENERAL', f"{self}")])

            wumpus = [
                hazard for hazard in hazards if hazard.hazard_type == 'WUMPUS'
            ][0]
            wumpus.move()

            # Check to see if any hazards are encountered in this cave.  Which, in most cases, will result in the
            # demise of the hunter.
            dangers = self.check_for_encounters(hazards)
            status.extend(dangers)

            # Determine whether the hunter encountered a bat colony.  That doesn't preclude the hunter being
            # deceased since the wumpus map also be resident in the same cave.
            encountered_bat_colony = [
                danger for danger in dangers if danger.source == 'BAT_COLONY'
            ]

            # If the hunter remains alive and s/he wasn't ferried off by bats after entering the cave, check to see
            # if any hazards are proximate to this cave and note the discoveries in the notebook.
            if self.alive and not encountered_bat_colony:
                warnings = self.check_for_hazards(hazards)
                status.extend(warnings)
                self.notebook.note_position(self.cave, warnings,
                                            not wumpus.asleep)

        # In the unlikely event that the user called the ajax post directly with an invalid cave id selection.
        else:
            errors.append(
                "The cave you specified does not adjoin the one you are in.")

        return status, errors
 def setUp(self):
     self.extractor = LineExtractor("")
     self.message = StatusMessage(self.extractor)
예제 #17
0
    def shoot(self, cave_id, hazards):
        """
        The hunter is shooting an arrow into the provided cave id.  The will either kill the wumpus or cause the wumpus
        to awaken and move about in search of the hunter.
        :param cave_id: the id of the cave the hunter is shooting into
        :param hazards: lising of the game's hazards
        :return: a tuple containing a list of status messages and a list of error messages, if any errors are found.
        """
        messages = []
        errors = []

        # Orientation message for the hunter (although s/he did not move)
        messages.extend([StatusMessage('INFO', 'GENERAL', f"{self}")])

        # Must have an arrow left to shoot in the first place.
        if self.quiver > 0:

            # Get the Wumpus object from the hazard list.  This is the only hazard of possible concern here since
            # the Wumpus is the only hazard that can move about.
            wumpus = [
                hazard for hazard in hazards if hazard.hazard_type == 'WUMPUS'
            ][0]

            # Spend the arrow and provide a message to that effect
            self.quiver -= 1
            messages.extend([
                StatusMessage(
                    'INFO', 'GENERAL',
                    f"You've shot an arrow into {cave_id}.  "
                    f"You have {self.quiver} arrows remaining.")
            ])

            # The wumpus may react to the shot in one of two ways.  It either dies or it wakes up (if not already
            # awake) and starts hunting the hunter
            messages.extend(wumpus.react_to_shot(cave_id))

            # If the wumpus is still alive, the wumpus will get a turn at finding the hunter
            if wumpus.alive:

                # Since the Wumpus is still alive, exhausting one's supply of arrows is really a death sentence.
                if self.quiver == 0:
                    messages.extend([
                        StatusMessage(
                            'WARNING', 'GENERAL',
                            "You have no arrows left.  All you can do is avoid the wumpus."
                        )
                    ])

                # This actually caused the Wumpus to move only if the Wumpus if awake.  Wumpus's do not walk in their
                # sleep.
                wumpus.move()

                # Check to see if the wumpus has entered this cave, which will result in the demise of the hunter.
                dangers = self.check_for_encounters([wumpus])
                messages.extend(dangers)

                # If the hunter remains alive, check to see if the wumpus is now proximate to this cave and note
                # that finding in the notebook.
                if self.alive:
                    warnings = self.check_for_hazards(hazards)
                    messages.extend(warnings)
                    self.notebook.note_position(self.cave, warnings,
                                                not wumpus.asleep)
        else:
            messages.extend([
                StatusMessage(
                    'WARNING', 'GENERAL',
                    "You have no arrows left.  All you can do is avoid the wumpus."
                )
            ])
        return messages, errors
예제 #18
0
 def setUp(self):
   self.extractor = LineExtractor("")
   self.message = StatusMessage(self.extractor)
   self.expander = Expander(Executor(), self.message)