def test_get_part_of_speech(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))

        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        pos = dictionary.get_part_of_speech("torch")
        self.assertEqual(pos, "noun")
    def test_lexer(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "take the torch from the table"
        tokens = parser.lexer(src)
        self.assertEqual(len(tokens), 6)
    def test_words(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))

        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        words = dictionary.words

        self.assertTrue(len(words), 16)
    def test_parser_with_adjectives_wrong(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "take the key rusty from the table"
        tree = parser.parse(src)

        self.assertFalse(tree)
    def test_verb(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "move"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertEqual(tree.verb.verb.word, "move")
    def test_parser_with_adjectives(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "take the rusty key from the table"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertTrue(tree.noun_phrase)
        self.assertEqual(tree.noun_phrase.modifier.adjective.word, "rusty")
    def test_parser_phrasal_verb(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "pick up the torch"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertEqual(tree.verb.verb.word, "pick up")
        self.assertEqual(tree.noun_phrase.noun.word, "torch")
    def test_parser_complex(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "take the torch from the table"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertTrue(tree.prep_phrase)
        self.assertEqual(tree.prep_phrase.prep.prep.word, "from")
        self.assertEqual(tree.prep_phrase.noun_phrase.noun.word, "table")
    def test_visitor_simple(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "move"
        tree = parser.parse(src)

        visitor = Visitor()
        command = tree.accept(visitor)
        self.assertTrue(command)
        self.assertEqual(command["verb"], "move")
Example #10
0
    def test_parser_with_adverb(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "move north"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertTrue(isinstance(tree, VerbPhrase))
        self.assertTrue(isinstance(tree.verb.verb.word, str))
        self.assertTrue(tree.verb.verb.word, "move")
        self.assertTrue(tree.adverb_phrase.adverb.word, "north")
Example #11
0
    def test_parser_simple(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "take the torch"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertTrue(isinstance(tree, VerbPhrase))
        self.assertTrue(isinstance(tree.verb.verb.word, str))
        self.assertTrue(tree.verb.verb.word, "take")
        self.assertTrue(tree.noun_phrase.noun.word, "torch")
Example #12
0
    def test_parser_phrasal_verb_complex(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "tests/dictionary.json")
        dictionary.load_words(filename)

        parser = Parser(dictionary)
        src = "pick up the rusty key from the table"
        tree = parser.parse(src)

        self.assertTrue(tree)
        self.assertEqual(tree.verb.verb.word, "pick up")
        self.assertEqual(tree.noun_phrase.modifier.adjective.word, "rusty")
        self.assertEqual(tree.noun_phrase.noun_phrase.noun.word, "key")
        self.assertEqual(tree.prep_phrase.prep.prep.word, "from")
        self.assertEqual(tree.prep_phrase.noun_phrase.noun.word, "table")
    def test_load_words(self):
        dictionary = Dictionary()
        filedir = os.path.dirname(os.path.realpath('__file__'))

        filename = os.path.join(filedir, "tests/dictionary.json")
        try:
            dictionary.load_words(filename)
        except Exception as e:
            self.assertTrue(False)

        self.assertTrue(len(dictionary.verbs), 3)
        self.assertTrue(len(dictionary.nouns), 3)
        self.assertTrue(len(dictionary.adverbs), 4)
        self.assertTrue(len(dictionary.prepositions), 3)
        self.assertTrue(len(dictionary.adjectives), 1)
        self.assertTrue(len(dictionary.articles), 3)
Example #14
0
class Engine:
    def __init__(self):
        self._id = 0
        self.playing = False
        self.moved = True
        self.enemy = None
        self.npc = None
        self.combatants = None
        self.explore_actions = {
            "move": self.move,
            "take": self.take,
            "look": self.look,
            "search": self.search,
            "unlock": self.unlock,
            "throw": self.throw,
            "drop": self.drop,
            "examine": self.examine,
            "trade": self.trade,
            "sleep": self.sleep,
            "speak": self.speak,
            "use": self.use,
            "consume": self.use,
            "equip": self.equip,
            "unequip": self.unequip,
            "mine": self.use
        }

    def init_crafting_systems(self):
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/crafting.json")
        systems = []

        with open(filename) as f:
            data = json.load(f)
            for system in data:
                systems.append(CraftingSystem(self.event_queue, **system))

        self.crafting_systems = systems

    def init_systems(self):
        self.dictionary = Dictionary()
        self.input_handler = InputHandler(self.dictionary)
        self.mob_factory = MobFactory()
        self.renderer = Renderer()
        self.event_queue = EventQueue()
        self.event_queue.register_system(self)
        self.message_log = MessageLog(self.event_queue)
        self.combat_system = CombatSystem(self.event_queue)
        self.init_crafting_systems()

    def init_game_data(self):
        self.world_map = WorldMap()

        # load rooms
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/rooms.json")
        self.world_map.load_rooms(filename)

        # load cutscenes
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/cutscenes.json")
        self.world_map.load_cutscenes(filename)

        # load items
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/items.json")
        self.world_map.load_items(filename)

        #load mobs
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/enemies.json")
        self.world_map.load_mobs(filename)

        # load npcs
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/npcs.json")
        self.world_map.load_npcs(filename)

        # load quests
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/quests.json")
        self.world_map.load_quests(filename)

        # load dictionary
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/dictionary.json")
        self.dictionary.load_words(filename)

    def create_player(self):
        filedir = os.path.dirname(os.path.realpath('__file__'))
        filename = os.path.join(filedir, "resources/player.json")
        with open(filename) as f:
            data = json.load(f)
            self.player = Player(**data)

    def get_crafting_system(self, name):
        """Retreive the specified crafting system."""

        for system in self.crafting_systems:
            if system.name == name:
                return system

    def list_items(self, container):
        """Builds a string containing all items in a container."""

        str = "{}{}{}"
        for i, _id in enumerate(container):
            item = self.world_map.items[_id]
            if i == 0:
                str = str.format(c.yellow, item.name, c.end)
                continue
            if i == len(container) - 1:
                str = "{} and {}{}{}".format(str, c.yellow, item.name, c.end)
                break
            str = "{}, {}{}{}".format(str, c.yellow, item.name, c.end)

        if str != "":
            if len(container) > 1:
                str = "You see the following items: {}".format(str)
            else:
                if str[0] == "a":
                    str = "You see an {}{}{}.".format(c.yellow, str, c.end)
                else:
                    str = "You see a {}{}{}.".format(c.yellow, str, c.end)
            self.event_queue.add_event({"message": str})

    def check_object(self, object, container, flag=0):
        """Checks if the specified object is in the passed container.

        flag = 0 represents an inanimate container (room or item), whereas
        flag = 1 represents an animate container (NPC).
        """

        if flag == 0:
            for _id in container:
                item = self.world_map.items[_id]
                if item.name.lower() == object:
                    return item

        elif flag == 1:
            names = [
                self.world_map.npcs[npc].name.lower() for npc in container
            ]
            if object in ["man", "woman"]:
                object = names[0]

            if object in names:
                for _id in container:
                    npc = self.world_map.npcs[_id]
                    if npc.name.lower() == object:
                        return npc
        else:
            return False

    def transition(self, from_id, to_id):
        """Represents the transition between moving from room A to room B."""

        from_room = self.world_map.get_room_at(from_id)
        to_room = self.world_map.get_room_at(to_id)
        self.moved = True

        self.world_map._id = to_id

        if not to_room.safe:
            if randrange(1, 100) < to_room.fight_chance:
                self.state = State.PRE_BATTLE
                mob_index = choice(to_room.mobs)
                mob_data = self.world_map.mobs[mob_index]
                mob = self.mob_factory.spawn(**mob_data)
                self.enemy = mob
                self.combatants = sorted([self.player, self.enemy],
                                         key=lambda entity: entity.speed)[::-1]
                return

        if to_room.tryattr("cutscene"):
            if to_room.cutscene == self.world_map.cutscene_id:
                for line in self.world_map.cutscenes[to_room.cutscene]["text"]:
                    self.event_queue.add_event({"message": line})
                self.world_map.cutscene_id += 1
                self.state = State.CUTSCENE
                return

    def move_player(self, action):
        """Moves the player in the direction specified if an exit exists."""

        exits = self.world_map.get_current_exits()
        index = self.world_map.directions.index(action["adverb"])
        if exits[index] != -1:
            self.transition(self.world_map._id, exits[index])
        else:
            msg = "There's no way out that way."
            self.event_queue.add_event({"message": msg})

    def move_item(self, action):
        """Passes to the 'use' method to call the 'move' script on an item."""
        self.use(action)

    def move(self, action):
        """Handles the two 'move' commands."""
        if (action["adverb"] in self.world_map.directions
                and not action["noun_phrase"]):
            self.move_player(action)
        else:
            self.move_item(action)

    def take(self, action):
        """Moves the specified item into the player's inventory."""

        items = [self.world_map.get_items_in_room()]
        object = action["noun_phrase"]
        prep = None
        indir_obj = None

        if action["prep_phrase"]:
            prep = action["prep_phrase"]["prep"]
            indir_obj = action["prep_phrase"]["noun_phrase"]

        if not object:
            self.event_queue.add_event({"message": "I don't understand that!"})
            return

        if indir_obj:
            switched = False
            item = self.check_object(indir_obj, items[0], flag=0)
            if item:
                if not item.locked:
                    items[0] = item.container
                    switched = True
                if item.locked:
                    self.event_queue.add_event({"message": "That's locked!"})
                    return

            if not switched:
                msg = "{}{}{} isn't here.".format(c.yellow, indir_obj, c.end)
                self.event_queue.add_event({"message": msg})
        else:
            for _id in items[0]:
                if self.world_map.items[_id].tryattr("container"):
                    if (self.world_map.items[_id].container
                            and self.world_map.items[_id].searched):
                        items.append(self.world_map.items[_id].container)

        taken = False
        attempted = False
        for container in items:
            item = self.check_object(object, container, flag=0)
            if item:
                if item.takeable:
                    if indir_obj:
                        msg = ("You take the {}{}{} from the {}{}{}.".format(
                            c.yellow, item.name, c.end, c.yellow, indir_obj,
                            c.end))
                        self.event_queue.add_event({"message": msg})
                    else:
                        msg = ("You take the {}{}{}.".format(
                            c.yellow, item.name, c.end))
                        self.event_queue.add_event({"message": msg})
                    self.player.container.append(item._id)
                    container.remove(item._id)
                    taken = True
                elif not item.takeable:
                    attempted = True
                    msg = "You're quite strong, but even you can't lift that..."
                    self.event_queue.add_event({"message": msg})

        if not taken and not attempted:
            msg = "{}{}{} isn't here.".format(c.yellow, object, c.end)
            self.event_queue.add_event({"message": msg})

        if taken:
            self.event_queue.add_event({"taken": item})

    def look_direction(self, action):
        """Reveals the description of the room in the specified direction."""

        exits = self.world_map.get_current_exits()
        index = self.world_map.directions.index(action["adverb"])

        if exits[index] == -1:
            msg = "You can't see anything of interest in that direction."
            self.event_queue.add_event({"message": msg})
        else:
            look_room = self.world_map.rooms[exits[index]]
            direction = action["adverb"]
            msg = ("You look {}{}{} and see the {}{}{}.".format(
                c.blue, direction, c.end, c.blue, look_room.name.title(),
                c.end))
            self.event_queue.add_event({"message": msg})

    def look_room(self, action):
        """Reveals items in, and extra info about, the current room."""

        ids = self.world_map.get_items_in_room()
        room = self.world_map.get_current_room()
        self.event_queue.add_event({"message": room.long_desc})

        if not ids:
            msg = "There's nothing of interest here..."
            self.event_queue.add_event({"message": msg})
        else:
            self.list_items(ids)

    def look(self, action):
        """Handles the two 'look' commands."""

        if action["adverb"] is not None:
            self.look_direction(action)
        else:
            self.look_room(action)

    def search(self, action):
        """Reveals any items stored within another object."""

        ids = self.world_map.get_items_in_room()
        object = action["noun_phrase"]

        item = self.check_object(object, ids, flag=0)

        if not item:
            msg = "You can't search something that's not here..."
            self.event_queue.add_event({"message": msg})
            return

        if item.tryattr("in_sight"):
            if item.in_sight:
                if item.container:
                    self.list_items(item.container)
                    item.searched = True
                else:
                    msg = "There's nothing here..."
                    self.event_queue.add_event({"message": msg})
            else:
                if item.tryattr("locked"):
                    if item.locked:
                        msg = "You can't see inside that."
                        self.event_queue.add_event({"message": msg})
                    else:
                        if item.container:
                            self.list_items(item.container)
                            item.searched = True
                        else:
                            msg = "There's nothing here..."
                            self.event_queue.add_event({"message": msg})

    def unlock(self, action):
        """Unlocks a locked object if the player has the right key."""

        object = action["noun_phrase"]
        unlocked = False
        items = self.world_map.get_items_in_room()
        item = self.check_object(object, items, flag=0)

        if item.tryattr("locked"):
            for _item in self.player.container:
                if item.locked_id == _item:
                    item.locked = False
                    self.player.container.remove(_item)
                    msg = "You unlocked the {}.".format(item.name)
                    self.event_queue.add_event({"message": msg})
                    unlocked = True

        if not unlocked:
            msg = "You don't have the right key for that..."
            self.event_queue.add_event({"message": msg})

    def collide(self, item_1, item_2, flag=0):
        """Represents the collision between two objects.

        flag = 0 means the target object is inanimate, flag = 1 means the player
        has thrown something at an NPC.
        """

        hit = randrange(1, 100) < 70

        if not hit:
            self.event_queue.add_event({
                "message":
                "You throw the {}{}{} but it misses!".format(
                    c.yellow, item_1.name, c.end)
            })
        else:
            if flag == 0:
                msg = ("You throw the {}{}{} at the {}{}{}.".format(
                    c.yellow, item_1.name, c.end, c.yellow, item_2.name,
                    c.end))
                self.event_queue.add_event({"message": msg})
                if item_2.breakable:
                    msg = (
                        "The {}{}{} strikes the {}{}{} and breaks it!".format(
                            c.yellow, item_1.name, c.end, c.yellow,
                            item_2.name, c.end))
                    self.event_queue.add_event({"message": msg})
                    if item_2.tryattr("container"):
                        room = self.world_map.get_current_room()
                        room.container += item_2.container
                        msg = ("The {}{}{} spills its contents onto the floor."
                               .format(c.yellow, item_2.name, c.end))
                        self.event_queue.add_event({"message": msg})
                        room.container.remove(item_2._id)
            elif flag == 1:
                msg = "Ow! Why'd you throw the {} at me???".format(item_1.name)
                self.event_queue.add_event({"message": msg})
                self.state = State.DIALOG
                self.npc = item_2

        self.player.container.remove(item_1._id)
        self.world_map.get_current_room().container.append(item_1._id)

    def throw(self, action):
        """Throw the specified item at another object."""

        object = action["noun_phrase"]
        prep = None
        indir_obj = None
        items = self.world_map.get_items_in_room()
        npcs = self.world_map.get_npcs_in_room()

        if action["prep_phrase"]:
            prep = action["prep_phrase"]["prep"]
            indir_obj = action["prep_phrase"]["noun_phrase"]

        if not object:
            msg = "I don't understand that..."
            self.event_queue.add_event({"message": msg})
            return

        if prep not in ["at"]:
            msg = "You have to throw something AT something else."
            self.event_queue.add_event({"message": msg})
            return

        if not indir_obj:
            msg = "You have to throw an object at something."
            self.event_queue.add_event({"message": msg})
            return

        item_2 = self.check_object(indir_obj, items)
        item_1 = self.check_object(object, self.player.container)

        if not item_1:
            msg = "You don't have one of those to throw."
            self.event_queue.add_event({"message": msg})
            return

        if not item_2:
            item_2 = self.check_object(indir_obj, npcs, flag=1)

            if not item_2:
                msg = "You can't throw an object at something that isn't here!"
                self.event_queue.add_event({"message": msg})
                return

        if item_2.tryattr("takeable"):
            self.collide(item_1, item_2)
        else:
            self.collide(item_1, item_2, flag=1)

    def drop(self, action):
        """Drop the specified item."""

        items = self.world_map.get_items_in_room()
        object = action["noun_phrase"]
        prep = None
        indir_obj = None

        item = self.check_object(object, self.player.container)

        if not item:
            if object in self.dictionary.words:
                msg = "You don't have one of those..."
                self.event_queue.add_event({"message": msg})
                return
            else:
                msg = "I don't understand that..."
                self.event_queue.add_event({"message": msg})
                return

        put = False
        if action["prep_phrase"]:
            prep = action["prep_phrase"]["prep"]
            indir_obj = action["prep_phrase"]["noun_phrase"]

        if prep:
            if prep in ["in", "on"]:
                indir_obj = self.check_object(indir_obj, items)
                indir_obj.container.append(item._id)
                self.player.container.remove(item._id)
                msg = ("You put the {}{}{} {} the {}{}{}".format(
                    c.yellow, item.name, c.end, prep, c.yellow, indir_obj.name,
                    c.end))
                self.event_queue.add_event({"message": msg})
            else:
                msg = "That doesn't make sense..."
                self.event_queue.add_event({"message": msg})
        else:
            room = self.world_map.get_current_room()
            room.container.append(item._id)
            self.player.container.remove(item._id)
            msg = ("You drop the {}{}{}.".format(c.yellow, item.name, c.end))
            self.event_queue.add_event({"message": msg})

    def examine(self, action):
        """Retreive the specified item's long description."""

        ids = self.world_map.get_items_in_room()
        object = action["noun_phrase"]
        item = None

        # make sure there is an object to examine
        if not object:
            msg = "You need to examine SOMETHING."
            self.event_queue.add_event({"message": msg})
            return

        # get item from room
        if ids:
            item = self.check_object(object, ids, flag=0)

        # get item from the player
        if not item:
            item = self.check_object(object, self.player.container, flag=0)

        # check player's equipped items
        if not item:
            all_slots = list(self.player.slots.values())
            equipped_items = [i for i in all_slots if i is not None]
            if equipped_items is not None:
                for id in equipped_items:
                    item = self.world_map.items[id]
                    if item.name == object:
                        break

        # get item from containers in the room
        if not item:
            room = self.world_map.get_current_room()
            for _id in ids:
                container = self.world_map.items[_id].container
                if container:
                    item = self.check_object(object, container, flag=0)
                    if item:
                        break

        if item:
            self.event_queue.add_event({"message": item.long_desc})
        else:
            msg = "You can't examine the {} if it's not here...".format(object)
            self.event_queue.add_event({"message": msg})

    def trade(self, action):

        pass

    def rest(self, on_floor=True):
        """Simulates resting and heals the player based on the type of bed."""
        for i in range(2):
            self.event_queue.add_event({"message": "ZZZzzzz"})

        if on_floor:
            self.player.hp += int(self.player.max_hp / 10)
            self.player.hp = min(self.player.hp, self.player.max_hp)
            self.event_queue.add_event(
                {"message": "You wake up feeling sore."})
        elif not on_floor:
            self.player.hp = self.player.max_hp
            self.event_queue.add_event({"message": "You feel well rested."})
        self.state = State.SLEEP

    def sleep(self, action):
        """Allows the player to 'sleep', providing there is a suitable bed."""

        prep_phrase = action.get("prep_phrase")
        prep = None
        object = None
        room = self.world_map.get_current_room()

        if prep_phrase:
            prep = action.get("prep_phrase")["prep"]
            object = action.get("prep_phrase")["noun_phrase"]

            if prep != "on":
                msg = "I don't understand that..."
                self.event_queue.add_event({"message": msg})
                return

            item = self.check_object(object, room.container)
            if item:
                if item.tryattr("sleep"):
                    # can sleep on it
                    self.rest(on_floor=False)
                else:
                    msg = "You can't sleep on that."
                    self.event_queue.add_event({"message": msg})
                    return
            else:
                if object == "floor":
                    self.rest(on_floor=True)
        else:
            msg = "You need to sleep ON something..."
            self.event_queue.add_event({"message": msg})

    def handle_quest(self, npc):
        """Starts or ends a quest with an NPC."""

        quest = self.world_map.quests[npc.quest_id]

        if quest.check_requirements(self.player):
            if npc.quest_id in self.player.quests:
                if quest.check_objective(self.player, self.event_queue):
                    for line in quest.outro:
                        self.event_queue.add_event({"message": line})
                    quest.on_completion(self.player, self.event_queue)
                    self.state = State.REWARD
            else:
                for line in quest.intro:
                    self.event_queue.add_event({"message": line})
                self.event_queue.add_event({"message": "y/n?"})

    def identify_npc(self, indir_obj, npcs):
        """Attempts to determine the NPC name when referred to indirectly."""

        if indir_obj in ["man", "gentleman"]:
            if self.world_map.npcs[npcs[0]].sex == 0:
                return self.world_map.npcs[npcs[0]].name.lower()
            else:
                msg = "There isn't a man here..."
                self.event_queue.add_event({"message": msg})
                return
        elif indir_obj in ["woman", "lady"]:
            if self.world_map.npcs[npcs[0]].sex == 1:
                return self.world_map.npcs[npcs[0]].name.lower()
            else:
                msg = "There isn't a woman here..."
                self.event_queue.add_event({"message": msg})
                return

    def introduce_npc(self, npc):
        """Introduces an NPC for the first time."""

        npc.known = True
        msg = ("My name's {}. Nice to meet you, friend.".format(
            npc.name.capitalize()))
        self.event_queue.add_event({"message": msg})
        self.event_queue.add_event({"meeting": npc})

    def speak(self, action):
        """Begins dialog with an NPC, leading potentially to quest giving."""

        npcs = self.world_map.get_npcs_in_room()
        prep = None
        indir_obj = None

        if action["prep_phrase"]:
            prep = action["prep_phrase"]["prep"]
            indir_obj = action["prep_phrase"]["noun_phrase"]

        if prep not in ["to", "with"]:
            self.event_queue.add_event(
                {"message": "That doesn't make sense..."})
            msg = ("Do you mean \"{} to\" or \"{} with\"?".format(
                action["verb"], action["verb"]))
            self.event_queue.add_event({"message": msg})
            return

        indir_obj = self.identify_npc(indir_obj, npcs)

        npc = self.check_object(indir_obj, npcs, flag=1)

        if not npc:
            names = [self.world_map.npcs[npc].name.lower() for npc in npcs]
            if indir_obj in names:
                msg = ("I'm sorry, but {}{}{} isn't here...".format(
                    c.green, indir_obj.capitalize(), c.end))
                self.event_queue.add_event({"message": msg})
                return
            else:
                if indir_obj[0] == "a":
                    msg = ("If you try talking to an {}{}{}, \
                                people will think you're mad.".format(
                        c.yellow, indir_obj, c.end))
                    self.event_queue.add_event({"message": msg})
                    return
                else:
                    msg = ("If you try talking to a {}{}{}, \
                                people will think you're mad.".format(
                        c.yellow, indir_obj, c.end))
                    self.event_queue.add_event({"message": msg})
                    return

        self.npc = npc

        self.state = State.DIALOG

        if not npc.known:
            self.introduce_npc(npc)
        else:
            self.event_queue.add_event({"message": choice(s.GREETINGS)})

        if npc.quest_id >= 0:
            self.handle_quest(npc)

    def use(self, action):
        """Activates an item's attached script."""

        object = action.get("noun_phrase")
        item = self.check_object(object, self.player.container)
        room = self.world_map.get_current_room()

        if not item:
            item = self.check_object(object, room.container)
            if not item:
                msg = "That doesn't make sense..."
                self.event_queue.add_event({"message": msg})
                return
            if item.takeable:
                item = None

        if item:
            if item.tryattr("script"):
                method_to_call = getattr(src.systems.scripts, item.script)
                kwargs = {
                    "world_map": self.world_map,
                    "event_queue": self.event_queue,
                    "player": self.player,
                    "item": item
                }
                method_to_call(**kwargs)
            else:
                self.event_queue.add_event({"message": "You can't do that..."})
        else:
            self.event_queue.add_event({"message": "You can't do that..."})

    def equip(self, action):
        """Equips the specified item into the relevant slot.

        If there is already an item in the relevant slot, this will be
        unequipped and placed into the player's inventory and the specified item
        will be equipped.
        """

        object = action.get("noun_phrase")

        item = self.check_object(object, self.player.container)

        if item:
            if item.tryattr("slot"):
                if not self.player.slots[item.slot]:
                    self.player.slots[item.slot] = item._id
                elif self.player.slots[item.slot]:
                    self.player.container.append(self.player.slot[item.slot])
                    self.player.slots[item.slot] = item._id
                msg = "You equip {}{}{}.".format(c.yellow, item.name, c.end)
                self.event_queue.add_event({"message": msg})
                self.player.container.remove(item._id)
            else:
                msg = "You can't equip that..."
                self.event_queue.add_event({"message": msg})
        else:
            msg = "That doesn't make sense..."
            self.event_queue.add_event({"message": msg})

    def unequip(self, action):
        """Unequips the specified item from the relevant slot."""

        object = action.get("noun_phrase")

        items = [i for i in list(self.player.slots.values()) if i]
        item = self.check_object(object, items)

        if item:
            if item.tryattr("slot"):
                self.player.slots[item.slot] = None
                self.player.container.append(item._id)
                msg = ("You unequip the {}{}{}".format(c.yellow, item.name,
                                                       c.end))
                self.event_queue.add_event({"message": msg})
        else:
            msg = "You haven't got that equipped."
            self.event_queue.add_event({"message": msg})

    def add_quest(self):
        """Adds a quest to the player's quest log."""

        quest = self.world_map.quests[self.npc.quest_id]
        self.player.quests.append(quest.id)
        msg = ("You accepted {}{}{} quest.".format(c.green, quest.name.title(),
                                                   c.end))
        self.event_queue.add_event({"message": msg})

    def choose(self, action):
        """Presents the player the choice to accept or deny a quest."""

        choice = action.get("choice")
        if choice:
            if choice == "yes":
                self.add_quest()
                self.progress_quests()
                self.npc = None
                self.state = State.EXPLORE
                return
            elif choice == "no":
                self.state = State.EXPLORE
                return

        quest = self.world_map.quests[self.npc.quest_id]
        if not quest.check_requirements(self.player):
            self.npc = None
            self.state = State.EXPLORE

    def loot(self):
        """Moves from the loot state back to the explore state after a fight."""

        self.moved = True
        self.state = State.EXPLORE

    def pre_fight(self):
        """Moves from the pre-fight state to the battle state."""

        self.state = State.BATTLE

    def game_over(self):
        """The player has died."""
        self.playing = False

    def save_data(self):
        """Saves the player, world and game state data."""

        try:
            with open("savegame.txt", "wb") as save_game:
                data = (self.player, self.world_map, self.state)
                pickle.dump(data, save_game)
                self.event_queue.add_event({"message": "GAME SAVED."})
        except Exception as e:
            raise e

    def load_data(self):
        """Loads the player, world and game state data."""

        try:
            with open("savegame.txt", "rb") as save_game:
                data = pickle.load(save_game)
                self.player = data[0]
                self.world_map = data[1]
                self.state = data[2]
        except Exception as e:
            raise e

    def explore(self):
        """Reverts back to the explore state."""

        self.state = State.EXPLORE

    def do_action(self, action):
        """Executes the player's command when in the explore state."""

        command = list(action.keys())[0]

        if command == "verb":
            verb = action.get("verb")
            if verb:
                self.explore_actions[verb](action)
        elif command == "quit":
            self.playing = False
        elif command in ["stats", "stat"]:
            self.state = State.CHAR_SCREEN
        elif command in ["j", "log", "journal"]:
            self.state = State.JOURNAL
        elif command in ["inventory"]:
            self.state = State.INVENTORY
        elif command in ["save"]:
            self.save_data()

    def validate_move(self, move, known_moves):
        """Checks whether the specified move is in known_moves.

        'move' is single character.
        """

        if len(move) > 1:
            return False

        if ord(move) - 97 in known_moves:
            return True
        else:
            return False

    def fight(self, action):
        """Execute combatant moves for this round of combat."""

        if action.get("command"):
            move = action.get("command")
            if self.validate_move(move, self.player.learnt_moves):
                enemy_move = self.enemy.choose_move()
                self.combatants = sorted([self.player, self.enemy],
                                         key=lambda entity: entity.speed)[::-1]
                # loop through combatants and conduct moves.
                for combatant in self.combatants:
                    if isinstance(combatant, Player):
                        move_index = self.player.moves[ord(move) - 97]
                        event_params = (self.player, self.enemy, move_index)
                        self.event_queue.add_event({"attack": event_params})
                        continue
                    if isinstance(combatant, Mob):
                        event_params = (self.enemy, self.player, enemy_move)
                        self.event_queue.add_event({"attack": event_params})
                        continue

    def level_up(self):
        """Level up the player."""

        lvl = self.player.level
        while self.player.xp >= self.player.next_xp:
            self.player.xp -= self.player.next_xp
            self.player.level += 1
            self.player.next_xp = 110 * lvl + 50 * (lvl - 1)
        msg = ("Congratulations! You advanced to {}level {}{}".format(
            c.yellow, self.player.level, c.end))
        self.event_queue.add_event({"message": msg})

    def check_level_up(self):
        """Check whether the player has reached the next level."""

        if self.player.xp >= self.player.next_xp:
            return True

    def xp_gain(self, event):
        """Gives xp to the player."""

        xp = event.get("xp")
        self.player.xp += xp
        msg = "You gained {}{}{} xp.".format(c.yellow, xp, c.end)
        self.event_queue.add_event({"message": msg})
        if self.check_level_up():
            return self.level_up()

    def money_gain(self, event):
        """Gives money to the player."""

        money = event.get("money")
        self.player.wallet += money
        msg = ("You received {}{}{} coins as a reward.".format(
            c.yellow, money, c.end))
        self.event_queue.add_event({"message": msg})

    def on_enemy_death(self):
        """Handles enemy death. Gives xp and money, and drops items."""
        room = self.world_map.get_current_room()
        room.container += self.enemy.container

        self.event_queue.inject({"xp": self.enemy.xp})

        money = randrange(self.enemy.money[0], self.enemy.money[1])
        self.player.wallet += money

        if money == -1:
            msg = (
                "I'm not sure the {}{}{} is going to have any money on it...".
                format(c.red, self.enemy.name, c.end))
            self.event_queue.add_event({"message": msg})
        elif money == 0:
            msg = ("You loot the {}{}{} and find nothing!".format(
                c.red, self.enemy.name, c.end))
            self.event_queue.add_event({"message": msg})
        elif money == 1:
            msg = ("You loot the {}{}{} and find {}{}{} coin.".format(
                c.red, self.enemy.name, c.end, c.yellow, money, c.end))
            self.event_queue.add_event({"message": msg})
        elif money > 1:
            msg = ("You loot the {}{}{} and find {}{}{} coins.".format(
                c.red, self.enemy.name, c.end, c.yellow, money, c.end))
            self.event_queue.add_event({"message": msg})

    def update_quests(self, message):
        """Moves a completed quest from current to completed list."""

        id = message.get("quest")
        self.player.quests.remove(id)
        self.player.completed_quests.append(id)

    def progress_quests(self, message):
        """Tracks the progress of active quests."""

        entity = message.get("dead")
        npc = message.get("meeting")
        item = message.get("taken")
        for id in self.player.quests:
            quest = self.world_map.quests[id]
            for objective in quest.objectives:
                if objective["quest_type"] == "KILL" and entity:
                    if objective["target"] == entity._id:
                        objective["progress"] += 1
                if objective["quest_type"] == "FIND" and npc:
                    if npc._id == objective["target"]:
                        objective["progress"] += 1
                if objective["quest_type"] == "COLLECT" and item:
                    if item._id == objective["target"]:
                        objective["progress"] += 1

    def reveal_exit(self, message):
        """Reveals hidden exits."""

        exit_update = message.get("exit")
        room = self.world_map.rooms[exit_update[0]]
        room.exits[exit_update[1]] = exit_update[2]
        msg = ("A new exit has been revealed in the {}{}{}.".format(
            c.blue, room.name, c.end))
        self.event_queue.add_event({"message": msg})

    def learn_skill(self, message):
        """Learns a new skill."""

        skill_info = message.get("skill")

        player_skill = getattr(self.player, skill_info[0])
        player_skill.append(skill_info[1])

        system = self.get_crafting_system(skill_info[0])
        skill_learnt = system.recipe_names[skill_info[1]]

        msg = ("You have learnt how to make {}{}{}.".format(
            c.yellow, skill_learnt, c.end))
        self.event_queue.add_event({"message": msg})

    def change_state(self, new_state):
        """Changes state as requested by an external object."""
        self.state = new_state.get("state")

    def start_game(self, action):
        """Starts up the game and determines whether to start anew or load."""
        if action.get("command") == "new_game":
            self.playing = True
        elif action.get("command") == "load_game":
            self.playing = True
            self.load_data()
        else:
            pass

    def smelt(self, action):
        """Begins the smelting crafting system."""

        if action.get("command"):
            if ord(action.get("command")) - 97 in self.player.smelting:
                event_params = (action.get("command"), self.player)
                self.event_queue.inject({"smelt": event_params})
            else:
                msg = "You haven't learnt that yet..."
                self.event_queue.add_event({"message": msg})
        else:
            self.state = State.EXPLORE

    def smith(self, action):
        """Begins the blacksmithing crafting system."""

        if action.get("command"):
            if ord(action.get("command")) - 97 in self.player.blacksmithing:
                event_params = (action.get("command"), self.player)
                self.event_queue.inject({"smith": event_params})
        else:
            self.state = State.EXPLORE

    def brew(self, action):
        """Begins the brewing crafting system."""

        if action.get("command"):
            if ord(action.get("command")) - 97 in self.player.brewing:
                event_params = (action.get("command"), self.player)
                self.event_queue.inject({"brew": event_params})
        else:
            self.state = State.EXPLORE

    def handle_input(self):
        """Handles input across all states."""

        action = self.input_handler.prompt(self.state)

        if isinstance(action, Exception):
            self.event_queue.add_event({"message": str(action)})
            return

        if self.state == State.EXPLORE:
            self.do_action(action)
        elif self.state == State.BATTLE:
            self.fight(action)
        elif self.state == State.LOOT:
            self.loot()
        elif self.state == State.PRE_BATTLE:
            self.pre_fight()
        elif self.state == State.GAME_OVER:
            self.game_over()
        elif self.state == State.CHAR_SCREEN:
            self.explore()
        elif self.state == State.DIALOG:
            self.choose(action)
        elif self.state == State.REWARD:
            self.explore()
        elif self.state == State.JOURNAL:
            self.explore()
        elif self.state == State.INVENTORY:
            self.explore()
        elif self.state == State.SLEEP:
            self.explore()
        elif self.state == State.START:
            self.start_game(action)
        elif self.state == State.CUTSCENE:
            self.explore()
        elif self.state == State.FURNACE:
            self.smelt(action)
        elif self.state == State.ANVIL:
            self.smith(action)

    def update(self):
        """Updates the game engine and other systems each turn."""

        self.combat_system.update(self.combatants)
        self.world_map.update()
        self.event_queue.processs_queue()

    def draw(self):
        """Passes all relevant information to the renderer to be 'drawn'."""

        self.renderer.draw(self.state,
                           player=self.player,
                           world=self.world_map,
                           messages=self.message_log.messages,
                           moved=self.moved,
                           enemy=self.enemy,
                           combat_system=self.combat_system,
                           npc=self.npc,
                           crafting_systems=self.crafting_systems)
        self.message_log.clear()

    def receive(self, message):
        """Handles the actioning of received events."""

        if message.get("xp"):
            self.xp_gain(message)
        elif message.get("dead"):
            if not isinstance(message.get("dead"), Player):
                self.on_enemy_death()
                self.progress_quests(message)
                self.enemy = None
                self.state = State.LOOT
        elif message.get("meeting"):
            self.progress_quests(message)
        elif message.get("taken"):
            self.progress_quests(message)
        elif message.get("game over"):
            self.state = State.GAME_OVER
        elif message.get("money"):
            self.money_gain(message)
        elif list(message.keys())[0] == "quest":
            self.update_quests(message)
        elif message.get("exit"):
            self.reveal_exit(message)
        elif message.get("skill"):
            self.learn_skill(message)
        elif message.get("state"):
            self.change_state(message)

    def begin(self):
        """Begins the game and initalises/loads all systems and data."""

        self.state = State.START
        self.init_systems()
        self.init_game_data()
        self.create_player()
        self.renderer.clear()
        self.draw()
        while (not self.playing):
            self.handle_input()
            if self.playing:
                break
            self.draw()
        # check whether a save file has been loaded
        if self.state == State.START:
            self.transition(0, 0)  # move into the game world.

    def run(self):
        """The main game loop."""

        self.begin()
        self.update()
        self.draw()

        while (self.playing):
            self.moved = False
            self.handle_input()
            self.update()
            self.draw()