Exemplo n.º 1
0
    def setUp(self):
        self.kb = KnowledgeBase()

        self.hero = facts.Hero(uid='hero')

        self.start = facts.Start(uid='start', type='test', nesting=0)
        self.state_1 = facts.State(uid='state_1')
        self.state_2 = facts.State(uid='state_2')
        self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0)

        self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero]

        self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter())
Exemplo n.º 2
0
 def __init__(self,
              knowledge_base,
              quests_stack=None,
              created_at=None,
              states_to_percents=None,
              hero=None):
     self.hero = hero
     self.quests_stack = [] if quests_stack is None else quests_stack
     self.knowledge_base = knowledge_base
     self.machine = Machine(knowledge_base=knowledge_base, interpreter=self)
     self.created_at = datetime.datetime.now(
     ) if created_at is None else created_at
     self.states_to_percents = states_to_percents if states_to_percents is not None else {}
Exemplo n.º 3
0
 def __init__(self, knowledge_base, quests_stack=None, created_at=None, states_to_percents=None, hero=None):
     self.hero = hero
     self.quests_stack = [] if quests_stack is None else quests_stack
     self.knowledge_base = knowledge_base
     self.machine = Machine(knowledge_base=knowledge_base, interpreter=self)
     self.created_at = datetime.datetime.now() if created_at is None else created_at
     self.states_to_percents = states_to_percents if states_to_percents is not None else {}
Exemplo n.º 4
0
    def setUp(self):
        self.kb = KnowledgeBase()

        self.hero = facts.Hero(uid='hero')

        self.start = facts.Start(uid='start', type='test', nesting=0)
        self.state_1 = facts.State(uid='state_1')
        self.state_2 = facts.State(uid='state_2')
        self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0)

        self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero]

        self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter())
Exemplo n.º 5
0
class QuestPrototype(object):

    def __init__(self, knowledge_base, quests_stack=None, created_at=None, states_to_percents=None, hero=None):
        self.hero = hero
        self.quests_stack = [] if quests_stack is None else quests_stack
        self.knowledge_base = knowledge_base
        self.machine = Machine(knowledge_base=knowledge_base,
                               interpreter=self)
        self.created_at =datetime.datetime.now() if created_at is None else created_at
        self.states_to_percents = states_to_percents if states_to_percents is not None else {}

    @property
    def current_info(self):
        return self.quests_stack[-1]

    def serialize(self):
        return {'quests_stack': [info.serialize() for info in self.quests_stack],
                'knowledge_base': self.knowledge_base.serialize(short=True),
                'created_at': time.mktime(self.created_at.timetuple()),
                'states_to_percents': self.states_to_percents,}

    @classmethod
    def deserialize(cls, data):
        return cls(knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'], fact_classes=facts.FACTS),
                   quests_stack=[QuestInfo.deserialize(info_data) for info_data in data['quests_stack']],
                   created_at=datetime.datetime.fromtimestamp(data['created_at']),
                   states_to_percents=data['states_to_percents'])

    @property
    def percents(self): return self.states_to_percents.get(self.machine.pointer.state, 0.0)

    @property
    def is_processed(self): return self.machine.is_processed

    def get_nearest_choice(self): return self.machine.get_nearest_choice()

    def make_choice(self, option_uid):
        choice, options, defaults = self.get_nearest_choice()

        if self.knowledge_base[option_uid].state_from != choice.uid:
            return False

        if not any(option.uid == option_uid for option in options):
            return False

        if not defaults[0].default:
            return False

        if not transformators.change_choice(knowledge_base=self.knowledge_base, new_option_uid=option_uid, default=False):
            return False

        self.machine.sync_pointer()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice())

        self.hero.actions.request_replane()
        return True

    ###########################################
    # Object operations
    ###########################################

    def process(self):
        self.hero.quests.mark_updated()

        step_result = self.machine.do_step()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero, *self.get_nearest_choice())

        if step_result:
            return self.percents

        return 1

    def _move_hero_to(self, destination, break_at=None):
        from the_tale.game.actions.prototypes import ActionMoveToPrototype

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        ActionMoveToPrototype.create(hero=self.hero, destination=destination, break_at=break_at)


    def _move_hero_near(self, destination, terrains=None, back=False):
        from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype

        if destination is None:
            destination = self.hero.position.get_nearest_dominant_place()

        ActionMoveNearPlacePrototype.create(hero=self.hero, place=destination, back=back, terrains=terrains)


    def _move_hero_on_road(self, place_from, place_to, percents):

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        path_to_position_length = self.hero.position.get_minumum_distance_to(place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        if percents <= current_percents:
            return

        path_to_pass = (percents - current_percents) * full_path_length

        self._move_hero_to(destination=place_to, break_at=path_to_pass / path_from_position_length)

    def get_current_power(self, power):
        return power * self.current_info.power

    def _give_person_power(self, hero, person, power):
        power = self.get_current_power(power)

        if power > 0:
            hero.places_history.add_place(person.place_id, value=person.attrs.places_help_amount)

        if not hero.can_change_person_power(person):
            return 0

        power = hero.modify_politics_power(power, person=person)

        power += ( 1 if power > 0 else -1) * self.current_info.power_bonus

        person_uid = uids.person(person.id)
        has_profession_marker = [marker for marker in self.knowledge_base.filter(facts.ProfessionMarker) if marker.person == person_uid]

        if has_profession_marker:
            power /= len(PERSON_TYPE.records)

        person.cmd_change_power(hero_id=hero.id,
                                has_place_in_preferences=hero.preferences.has_place_in_preferences(person.place),
                                has_person_in_preferences=hero.preferences.has_person_in_preferences(person),
                                power=power)

        return power


    def _give_place_power(self, hero, place, power):
        power = self.get_current_power(power)

        if power > 0:
            hero.places_history.add_place(place.id, value=1)

        if not hero.can_change_place_power(place):
            return 0

        power = hero.modify_politics_power(power, place=place)

        place.cmd_change_power(hero_id=hero.id,
                               has_place_in_preferences=hero.preferences.has_place_in_preferences(place),
                               has_person_in_preferences=False,
                               power=power)

        return power

    def _fight(self, action):
        from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype

        if action.mob is not None:
            mob = mobs_storage[self.knowledge_base[action.mob].externals['id']].create_mob(self.hero, is_boss=True)
        else:
            mob = mobs_storage.get_random_mob(self.hero, mercenary=action.mercenary, is_boss=True)

            if mob is None:
                mobs_storage.get_random_mob(self.hero, is_boss=True)


        ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob)

    def give_social_power(self, quest_results):
        results = {}

        for object_uid, result in quest_results.iteritems():
            object_fact = self.knowledge_base[object_uid]

            if not isinstance(object_fact, facts.Person):
                continue

            person_id = object_fact.externals['id']

            if person_id not in persons_storage.persons:
                continue

            results[persons_storage.persons[person_id]] = result

        VALUABLE_RESULTS = (QUEST_RESULTS.SUCCESSED, QUEST_RESULTS.FAILED)

        for (person_1, quest_1_result), (person_2, quest_2_result) in itertools.combinations(results.iteritems(), 2):

            if quest_1_result not in VALUABLE_RESULTS or quest_2_result not in VALUABLE_RESULTS:
                continue

            connection_type = persons_storage.social_connections.get_connection_type(person_1, person_2)

            if connection_type is None:
                continue

            if ( not ( (connection_type.is_PARTNER and quest_1_result == quest_2_result) or
                       (connection_type.is_CONCURRENT and quest_1_result != quest_2_result) ) ):
                continue

            self._give_person_power(hero=self.hero,
                                    person=person_1,
                                    power=1 if quest_1_result == QUEST_RESULTS.SUCCESSED else -1)

            self._give_person_power(hero=self.hero,
                                    person=person_2,
                                    power=1 if quest_2_result == QUEST_RESULTS.SUCCESSED else -1)



    def _finish_quest(self, finish, hero):

        experience = self.current_info.experience
        experience_bonus = self.current_info.experience_bonus

        hero.add_experience(experience)
        hero.add_experience(experience_bonus, without_modifications=True)

        hero.statistics.change_quests_done(1)

        if hero.companion:
            hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST)

        for object_uid, result in finish.results.iteritems():
            if result == QUEST_RESULTS.SUCCESSED:
                object_politic_power = 1
            elif result == QUEST_RESULTS.FAILED:
                object_politic_power = -1
            else:
                object_politic_power = 0

            object_fact = self.knowledge_base[object_uid]

            if isinstance(object_fact, facts.Person):
                person_id = object_fact.externals['id']

                if result == QUEST_RESULTS.FAILED:
                    hero.quests.add_interfered_person(person_id)

                person_habits_change_source = persons_storage.persons[person_id].attrs.on_quest_habits.get(result)

                if person_habits_change_source:
                    self.hero.update_habits(person_habits_change_source)

                self._give_person_power(self.hero, persons_storage.persons[person_id], object_politic_power)

            elif isinstance(object_fact, facts.Place):
                self._give_place_power(self.hero, places_storage.places[object_fact.externals['id']], object_politic_power)

            else:
                raise exceptions.UnknownPowerRecipientError(recipient=object_fact)

        for marker, default in self.current_info.used_markers.iteritems():
            for change_source in HABIT_CHANGE_SOURCE.records:
                if change_source.quest_marker == marker and change_source.quest_default == default:
                    self.hero.update_habits(change_source)

        self.give_social_power(finish.results)

        self.quests_stack.pop()


    def get_state_by_jump_pointer(self):
        return self.knowledge_base[self.knowledge_base[self.machine.pointer.jump].state_to]


    def positive_results_persons(self):

        finish_state = self.get_state_by_jump_pointer()

        for object_uid, result in finish_state.results.iteritems():

            if result != QUEST_RESULTS.SUCCESSED:
                continue

            object_fact = self.knowledge_base[object_uid]

            if not isinstance(object_fact, facts.Person):
                continue

            person_id = object_fact.externals['id']

            yield persons_storage.persons[person_id]



    def modify_reward_scale(self, scale):
        for person in self.positive_results_persons():
            scale += person.attrs.on_profite_reward_bonus

        return scale


    def give_energy_on_reward(self):
        for person in self.positive_results_persons():
            self.hero.add_energy_bonus(person.attrs.on_profite_energy)

    def _give_reward(self, hero, reward_type, scale):

        quest_info = self.current_info

        scale = quest_info.get_real_reward_scale(hero, scale)
        scale = self.modify_reward_scale(scale)

        self.give_energy_on_reward()

        # hero receive artifact
        if hero.can_get_artifact_for_quest():

            level_delta = int(math.ceil(abs(scale)))

            if scale < 0:
                level_delta = -level_delta

            artifact, unequipped, sell_price = self.hero.receive_artifact(equip=False,
                                                                          better=False,
                                                                          prefered_slot=False,
                                                                          prefered_item=False,
                                                                          archetype=False,
                                                                          level_delta=level_delta)

            if artifact is not None:
                quest_info.process_message(knowledge_base=self.knowledge_base,
                                           hero=self.hero,
                                           message='%s_artifact' % reward_type,
                                           ext_substitution={'artifact': artifact})
                return

        # here does not receive artifac (receive money instead)

        money = int(max(1, f.sell_artifact_price(hero.level) * scale))

        hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money)

        quest_info.process_message(knowledge_base=self.knowledge_base,
                                   hero=self.hero,
                                   message='%s_money' % reward_type,
                                   ext_substitution={'coins': money})

    def _donothing(self, donothing_type):
        from the_tale.game.actions.prototypes import ActionDoNothingPrototype

        donothing = relations.DONOTHING_TYPE.index_value[donothing_type]

        writer = writers.get_writer(hero=self.hero, type=self.current_info.type, message=donothing_type, substitution={})

        ActionDoNothingPrototype.create(hero=self.hero,
                                        duration=donothing.duration,
                                        messages_prefix=writer.journal_id(),
                                        messages_probability=donothing.messages_probability)


    @classmethod
    def _get_upgrdade_choice(cls, hero):
        choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY]

        if hero.preferences.equipment_slot and hero.equipment.get(hero.preferences.equipment_slot) is not None:
            choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP)

            if hero.equipment.get(hero.preferences.equipment_slot).can_be_broken():
                choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR)

        return random.choice(choices)

    @classmethod
    def upgrade_equipment_cost(cls, hero_info):
        return int(f.normal_action_price(hero_info.level) * ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction())

    @classmethod
    def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost):

        if cost is not None:
            cost = min(cost, hero.money)

        upgrade_choice = cls._get_upgrdade_choice(hero)

        if upgrade_choice.is_BUY:
            artifact, unequipped, sell_price = hero.receive_artifact(equip=True, better=True, prefered_slot=True, prefered_item=True, archetype=True)

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost)
                if artifact is None:
                    process_message(knowledge_base, hero, message='upgrade__fail', ext_substitution={'coins': cost})
                elif unequipped:
                    process_message(knowledge_base, hero, message='upgrade__buy_and_change', ext_substitution={'coins': cost,
                                                                                                               'artifact': artifact,
                                                                                                               'unequipped': unequipped,
                                                                                                               'sell_price': sell_price})
                else:
                    process_message(knowledge_base, hero, message='upgrade__buy', ext_substitution={'coins': cost,
                                                                                                    'artifact': artifact})
            else:
                if artifact is None:
                    process_message(knowledge_base, hero, message='upgrade_free__fail', ext_substitution={})
                elif unequipped:
                    process_message(knowledge_base, hero, message='upgrade_free__buy_and_change', ext_substitution={'artifact': artifact,
                                                                                                                    'unequipped': unequipped,
                                                                                                                    'sell_price': sell_price})
                else:
                    process_message(knowledge_base, hero, message='upgrade_free__buy', ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_SHARP:
            artifact = hero.sharp_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost)
                process_message(knowledge_base, hero, message='upgrade__sharp', ext_substitution={'coins': cost,
                                                                                                  'artifact': artifact})
            else:
                process_message(knowledge_base, hero, message='upgrade_free__sharp', ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_REPAIR:
            artifact = hero.repair_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost)
                process_message(knowledge_base, hero, message='upgrade__repair', ext_substitution={'coins': cost,
                                                                                                   'artifact': artifact})
            else:
                process_message(knowledge_base, hero, message='upgrade_free__repair', ext_substitution={'artifact': artifact})

        else:
            raise exceptions.UnknownUpgadeEquipmentTypeError(type=upgrade_choice)

    def _start_quest(self, start, hero):
        hero.quests.update_history(start.type, TimePrototype.get_current_turn_number())
        self.quests_stack.append(QuestInfo.construct(type=start.type,
                                                     uid=start.uid,
                                                     knowledge_base=self.machine.knowledge_base,
                                                     experience=self.get_expirience_for_quest(start.uid, hero),
                                                     power=self.get_politic_power_for_quest(start.uid, hero),
                                                     hero=hero))

    def quest_participants(self, quest_uid):
        for participant in self.knowledge_base.filter(facts.QuestParticipant):

            if quest_uid != participant.start:
                continue

            yield participant

    def get_expirience_for_quest(self, quest_uid, hero):
        experience = f.experience_for_quest(c.QUEST_AREA_RADIUS)

        if hero.statistics.quests_done == 0:
            # since we get shortest path for first quest
            # and want to give exp as fast as can
            # and do not want to give more exp than level up required
            experience = int(experience/2)

        place_experience_bonuses = {}
        person_experience_bonuses = {}

        for participant in self.quest_participants(quest_uid):

            fact = self.knowledge_base[participant.participant]

            if isinstance(fact, facts.Person):
                person = persons_storage.persons.get(fact.externals['id'])
                person_experience_bonuses[person.id] = person.attrs.experience_bonus
                place = person.place
            elif isinstance(fact, facts.Place):
                place = places_storage.places.get(fact.externals['id'])

            place_experience_bonuses[place.id] = place.attrs.experience_bonus

        experience += experience * (sum(place_experience_bonuses.values()) + sum(person_experience_bonuses.values()))

        return experience


    def get_politic_power_for_quest(self, quest_uid, hero):
        base_politic_power = f.person_power_for_quest(c.QUEST_AREA_RADIUS)

        for participant in self.quest_participants(quest_uid):

            fact = self.knowledge_base[participant.participant]

            if isinstance(fact, facts.Person):
                person = persons_storage.persons.get(fact.externals['id'])
                base_politic_power += person.attrs.politic_power_bonus

        return base_politic_power


    ################################
    # general callbacks
    ################################

    def on_state__before_actions(self, state):

        if isinstance(state, facts.Start):
            self._start_quest(state, hero=self.hero)

    def on_state__after_actions(self, state):
        if isinstance(state, facts.Finish):
            self._finish_quest(state, hero=self.hero)

    def on_jump_start__before_actions(self, jump):
        pass

    def on_jump_start__after_actions(self, jump):
        pass

    def on_jump_end__before_actions(self, jump):
        pass

    def on_jump_end__after_actions(self, jump):
        if not isinstance(jump, facts.Option):
            return

        path = (path for path in self.knowledge_base.filter(facts.ChoicePath) if path.option == jump.uid).next()

        used_markers = self.current_info.used_markers
        for marker in jump.markers:
            for markers_group in questgen_relations.OPTION_MARKERS_GROUPS:
                if marker not in markers_group:
                    continue

                for removed_marker in markers_group:
                    if removed_marker in used_markers:
                        del used_markers[removed_marker]

            used_markers[marker] = path.default


    ################################
    # do action callbacks
    ################################

    def do_message(self, action):
        self.current_info.process_message(self.knowledge_base, self.hero, action.type)

    def do_give_reward(self, action):
        self._give_reward(self.hero, action.type, action.scale)

    def do_fight(self, action):
        self._fight(action)

    def do_do_nothing(self, action):
        self._donothing(action.type)

    def do_upgrade_equipment(self, action):
        self._upgrade_equipment(self.current_info.process_message, self.hero, self.knowledge_base, cost=action.cost)

    def do_move_near(self, action):
        if action.place:
            self._move_hero_near(destination=places_storage.places.get(self.knowledge_base[action.place].externals['id']), terrains=action.terrains)
        else:
            self._move_hero_near(destination=None, terrains=action.terrains)

    ################################
    # check requirements callbacks
    ################################

    def check_located_in(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage.places[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            person = persons_storage.persons[object_fact.externals['id']]
            return person.place.id == place.id

        if isinstance(object_fact, facts.Hero):
            return bool(self.hero.id == object_fact.externals['id'] and self.hero.position.place and self.hero.position.place.id == place.id)

        raise exceptions.UnknownRequirementError(requirement=requirement)


    def check_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage.places[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            if self.hero.position.place:
                return False

            hero_place = self.hero.position.get_nearest_dominant_place()
            return place.id == hero_place.id

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if not isinstance(object_fact, facts.Hero):
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if self.hero.id != object_fact.externals['id']:
            return False

        place_from = places_storage.places[self.knowledge_base[requirement.place_from].externals['id']]
        place_to = places_storage.places[self.knowledge_base[requirement.place_to].externals['id']]
        percents = requirement.percents

        path_to_position_length = self.hero.position.get_minumum_distance_to(place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        return current_percents >= percents


    def check_has_money(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False
            return self.hero.money >= requirement.money

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_is_alive(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return True

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            return self.hero.is_alive

        raise exceptions.UnknownRequirementError(requirement=requirement)

    ################################
    # satisfy requirements callbacks
    ################################

    def satisfy_located_in(self, requirement):
        from . import logic

        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            person_uid = object_fact.uid
            person = persons_storage.persons[object_fact.externals['id']]

            new_place_uid= uids.place(person.place.id)

            if new_place_uid not in self.knowledge_base:
                self.knowledge_base += logic.fact_place(person.place)

            # переписываем все ограничения в базе
            for fact in self.knowledge_base.filter(facts.State):
                for state_requirement in fact.require:
                    if isinstance(state_requirement, requirements.LocatedIn) and state_requirement.object == person_uid:
                        state_requirement.place = new_place_uid

            return

        if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_to(destination=places_storage.places[self.knowledge_base[requirement.place].externals['id']])

    def satisfy_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if requirement.place is None:
            self._move_hero_near(destination=None, terrains=requirement.terrains)
        else:
            self._move_hero_near(destination=places_storage.places.get(self.knowledge_base[requirement.place].externals['id']), terrains=requirement.terrains)

    def satisfy_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(object_fact, facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_on_road(place_from=places_storage.places[self.knowledge_base[requirement.place_from].externals['id']],
                                place_to=places_storage.places[self.knowledge_base[requirement.place_to].externals['id']],
                                percents=requirement.percents)

    def satisfy_has_money(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)

    def satisfy_is_alive(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)


    ################################
    # ui info
    ################################

    def ui_info(self):
        return {'line': [info.ui_info(self.hero) for info in self.quests_stack]}

    @classmethod
    def no_quests_ui_info(cls, in_place):
        if in_place:
            return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]}
        else:
            return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]}

    @classmethod
    def next_spending_ui_info(cls, spending):
        NEXT_SPENDING_INFO = QuestInfo(type='next-spending',
                                       uid='next-spending',
                                       name=u'Накопить золото',
                                       action=u'копит',
                                       choice=None,
                                       choice_alternatives=(),
                                       experience=0,
                                       power=0,
                                       experience_bonus=0,
                                       power_bonus=0,
                                       actors={'goal': (spending, u'цель')},
                                       used_markers=set())
        return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
Exemplo n.º 6
0
class QuestPrototype(object):
    def __init__(self,
                 knowledge_base,
                 quests_stack=None,
                 created_at=None,
                 states_to_percents=None,
                 hero=None):
        self.hero = hero
        self.quests_stack = [] if quests_stack is None else quests_stack
        self.knowledge_base = knowledge_base
        self.machine = Machine(knowledge_base=knowledge_base, interpreter=self)
        self.created_at = datetime.datetime.now(
        ) if created_at is None else created_at
        self.states_to_percents = states_to_percents if states_to_percents is not None else {}

    @property
    def current_info(self):
        return self.quests_stack[-1]

    def serialize(self):
        return {
            'quests_stack': [info.serialize() for info in self.quests_stack],
            'knowledge_base': self.knowledge_base.serialize(short=True),
            'created_at': time.mktime(self.created_at.timetuple()),
            'states_to_percents': self.states_to_percents,
        }

    @classmethod
    def deserialize(cls, data):
        return cls(
            knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'],
                                                     fact_classes=facts.FACTS),
            quests_stack=[
                QuestInfo.deserialize(info_data)
                for info_data in data['quests_stack']
            ],
            created_at=datetime.datetime.fromtimestamp(data['created_at']),
            states_to_percents=data['states_to_percents'])

    @property
    def percents(self):
        return self.states_to_percents.get(self.machine.pointer.state, 0.0)

    @property
    def is_processed(self):
        return self.machine.is_processed

    def get_nearest_choice(self):
        return self.machine.get_nearest_choice()

    def make_choice(self, option_uid):
        choice, options, defaults = self.get_nearest_choice()

        if self.knowledge_base[option_uid].state_from != choice.uid:
            return False

        if not any(option.uid == option_uid for option in options):
            return False

        if not defaults[0].default:
            return False

        if not transformators.change_choice(knowledge_base=self.knowledge_base,
                                            new_option_uid=option_uid,
                                            default=False):
            return False

        self.machine.sync_pointer()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero,
                                           *self.get_nearest_choice())

        self.hero.actions.request_replane()
        return True

    ###########################################
    # Object operations
    ###########################################

    def process(self):
        self.hero.quests.mark_updated()

        step_result = self.machine.do_step()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero,
                                           *self.get_nearest_choice())

        if step_result:
            return self.percents

        return 1

    def _move_hero_to(self, destination, break_at=None):
        from the_tale.game.actions.prototypes import ActionMoveToPrototype

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        ActionMoveToPrototype.create(hero=self.hero,
                                     destination=destination,
                                     break_at=break_at)

    def _move_hero_near(self, destination, terrains=None, back=False):
        from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype

        if destination is None:
            destination = self.hero.position.get_nearest_dominant_place()

        ActionMoveNearPlacePrototype.create(hero=self.hero,
                                            place=destination,
                                            back=back,
                                            terrains=terrains)

    def _move_hero_on_road(self, place_from, place_to, percents):

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        path_to_position_length = self.hero.position.get_minumum_distance_to(
            place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(
            place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        if percents <= current_percents:
            return

        path_to_pass = (percents - current_percents) * full_path_length

        self._move_hero_to(destination=place_to,
                           break_at=path_to_pass / path_from_position_length)

    def get_current_power(self, power):
        return power * self.current_info.power

    def modify_person_power(self, power, person):
        power += (1 if power > 0 else -1) * self.current_info.power_bonus

        person_uid = uids.person(person.id)
        has_profession_marker = [
            marker
            for marker in self.knowledge_base.filter(facts.ProfessionMarker)
            if marker.person == person_uid
        ]

        if has_profession_marker:
            power /= len(PERSON_TYPE.records)

        return power

    def _fight(self, action):
        from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype

        if action.mob is not None:
            mob = mobs_storage.mobs[self.knowledge_base[
                action.mob].externals['id']].create_mob(self.hero,
                                                        is_boss=True)
        else:
            mob = mobs_storage.mobs.get_random_mob(self.hero,
                                                   mercenary=action.mercenary,
                                                   is_boss=True)

            if mob is None:
                mobs_storage.mobs.get_random_mob(self.hero, is_boss=True)

        ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob)

    def _finish_quest(self, finish, hero):

        experience = self.current_info.experience
        experience_bonus = self.current_info.experience_bonus

        hero.add_experience(experience)
        hero.add_experience(experience_bonus, without_modifications=True)

        hero.statistics.change_quests_done(1)

        if hero.companion:
            hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST)

        power_impacts = []

        for object_uid, result in finish.results.items():

            object_fact = self.knowledge_base[object_uid]

            if isinstance(object_fact, facts.Person):
                person_id = object_fact.externals['id']

                if result == QUEST_RESULTS.FAILED:
                    hero.quests.add_interfered_person(person_id)

                person_habits_change_source = persons_storage.persons[
                    person_id].attrs.on_quest_habits.get(result)

                if person_habits_change_source:
                    self.hero.update_habits(person_habits_change_source)

                power = self.finish_quest_person_power(result, object_uid)

                power_impacts.extend(
                    persons_logic.impacts_from_hero(
                        self.hero, persons_storage.persons[person_id], power))

            elif isinstance(object_fact, facts.Place):
                power = self.finish_quest_place_power(result, object_uid)

                power_impacts.extend(
                    places_logic.impacts_from_hero(
                        self.hero,
                        places_storage.places[object_fact.externals['id']],
                        power))

            else:
                raise exceptions.UnknownPowerRecipientError(
                    recipient=object_fact)

        politic_power_logic.add_power_impacts(power_impacts)

        for marker, default in self.current_info.used_markers.items():
            for change_source in HABIT_CHANGE_SOURCE.records:
                if change_source.quest_marker == marker and change_source.quest_default == default:
                    self.hero.update_habits(change_source)

        self.quests_stack.pop()

    def finish_quest_power(self, result):
        if result == QUEST_RESULTS.SUCCESSED:
            object_politic_power = 1
        elif result == QUEST_RESULTS.FAILED:
            object_politic_power = -1
        else:
            object_politic_power = 0

        object_politic_power = self.get_current_power(object_politic_power)

        power_bonus = 0

        if result != QUEST_RESULTS.NEUTRAL:
            power_bonus = (1 if object_politic_power > 0 else
                           -1) * self.current_info.power_bonus

        return object_politic_power, power_bonus

    def finish_quest_person_power(self, result, object_uid):
        object_politic_power, power_bonus = self.finish_quest_power(result)

        has_profession_marker = [
            marker
            for marker in self.knowledge_base.filter(facts.ProfessionMarker)
            if marker.person == object_uid
        ]

        if has_profession_marker:
            object_politic_power /= len(PERSON_TYPE.records)

        return object_politic_power + power_bonus

    def finish_quest_place_power(self, result, object_uid):
        object_politic_power, power_bonus = self.finish_quest_power(result)
        return object_politic_power + power_bonus

    def get_state_by_jump_pointer(self):
        return self.knowledge_base[self.knowledge_base[
            self.machine.pointer.jump].state_to]

    def positive_results_persons(self):

        finish_state = self.get_state_by_jump_pointer()

        for object_uid, result in finish_state.results.items():

            if result != QUEST_RESULTS.SUCCESSED:
                continue

            object_fact = self.knowledge_base[object_uid]

            if not isinstance(object_fact, facts.Person):
                continue

            person_id = object_fact.externals['id']

            yield persons_storage.persons[person_id]

    def modify_reward_scale(self, scale):
        for person in self.positive_results_persons():
            scale += person.attrs.on_profite_reward_bonus

        return scale

    def give_energy_on_reward(self):
        if not self.hero.can_regenerate_energy:
            return

        energy = sum(person.attrs.on_profite_energy
                     for person in self.positive_results_persons())

        if energy == 0:
            return

        tt_api_energy.change_energy_balance(account_id=self.hero.account_id,
                                            type='for_quest',
                                            energy=energy,
                                            async=True,
                                            autocommit=True)

    def _give_reward(self, hero, reward_type, scale):

        quest_info = self.current_info

        scale = quest_info.get_real_reward_scale(hero, scale)
        scale = self.modify_reward_scale(scale)

        self.give_energy_on_reward()

        # hero receive artifact
        if hero.can_get_artifact_for_quest():

            level_delta = int(math.ceil(abs(scale)))

            if scale < 0:
                level_delta = -level_delta

            artifact, unequipped, sell_price = self.hero.receive_artifact(
                equip=False,
                better=False,
                prefered_slot=False,
                prefered_item=False,
                archetype=True,
                level_delta=level_delta)

            if artifact is not None:
                quest_info.process_message(
                    knowledge_base=self.knowledge_base,
                    hero=self.hero,
                    message='%s_artifact' % reward_type,
                    ext_substitution={'artifact': artifact})
                return

        # here does not receive artifac (receive money instead)

        money = int(max(1, f.sell_artifact_price(hero.level) * scale))

        hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money)

        quest_info.process_message(knowledge_base=self.knowledge_base,
                                   hero=self.hero,
                                   message='%s_money' % reward_type,
                                   ext_substitution={'coins': money})

    def _donothing(self, donothing_type):
        from the_tale.game.actions.prototypes import ActionDoNothingPrototype

        donothing = relations.DONOTHING_TYPE.index_value[donothing_type]

        writer = writers.get_writer(hero=self.hero,
                                    type=self.current_info.type,
                                    message=donothing_type,
                                    substitution={})

        ActionDoNothingPrototype.create(
            hero=self.hero,
            duration=donothing.duration,
            messages_prefix=writer.journal_id(),
            messages_probability=donothing.messages_probability)

    @classmethod
    def _get_upgrdade_choice(cls, hero):
        choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY]

        if hero.preferences.equipment_slot and hero.equipment.get(
                hero.preferences.equipment_slot) is not None:
            choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP)

            if hero.equipment.get(
                    hero.preferences.equipment_slot).can_be_broken():
                choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR)

        return random.choice(choices)

    @classmethod
    def upgrade_equipment_cost(cls, hero_info):
        return int(
            f.normal_action_price(hero_info.level) *
            ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction())

    @classmethod
    def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost):

        if cost is not None:
            cost = min(cost, hero.money)

        upgrade_choice = cls._get_upgrdade_choice(hero)

        if upgrade_choice.is_BUY:
            artifact, unequipped, sell_price = hero.receive_artifact(
                equip=True,
                better=True,
                prefered_slot=True,
                prefered_item=True,
                archetype=True)

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost)
                if artifact is None:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__fail',
                                    ext_substitution={'coins': cost})
                elif unequipped:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__buy_and_change',
                                    ext_substitution={
                                        'coins': cost,
                                        'artifact': artifact,
                                        'unequipped': unequipped,
                                        'sell_price': sell_price
                                    })
                else:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__buy',
                                    ext_substitution={
                                        'coins': cost,
                                        'artifact': artifact
                                    })
            else:
                if artifact is None:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__fail',
                                    ext_substitution={})
                elif unequipped:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__buy_and_change',
                                    ext_substitution={
                                        'artifact': artifact,
                                        'unequipped': unequipped,
                                        'sell_price': sell_price
                                    })
                else:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__buy',
                                    ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_SHARP:
            artifact = hero.sharp_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost)
                process_message(knowledge_base,
                                hero,
                                message='upgrade__sharp',
                                ext_substitution={
                                    'coins': cost,
                                    'artifact': artifact
                                })
            else:
                process_message(knowledge_base,
                                hero,
                                message='upgrade_free__sharp',
                                ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_REPAIR:
            artifact = hero.repair_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost)
                process_message(knowledge_base,
                                hero,
                                message='upgrade__repair',
                                ext_substitution={
                                    'coins': cost,
                                    'artifact': artifact
                                })
            else:
                process_message(knowledge_base,
                                hero,
                                message='upgrade_free__repair',
                                ext_substitution={'artifact': artifact})

        else:
            raise exceptions.UnknownUpgadeEquipmentTypeError(
                type=upgrade_choice)

    def _start_quest(self, start, hero):
        hero.quests.update_history(start.type, turn.number())
        self.quests_stack.append(
            QuestInfo.construct(
                type=start.type,
                uid=start.uid,
                knowledge_base=self.machine.knowledge_base,
                experience=self.get_expirience_for_quest(start.uid, hero),
                power=self.get_politic_power_for_quest(start.uid, hero),
                hero=hero))

    def quest_participants(self, quest_uid):
        for participant in self.knowledge_base.filter(facts.QuestParticipant):

            if quest_uid != participant.start:
                continue

            yield participant

    def get_expirience_for_quest(self, quest_uid, hero):
        experience = f.experience_for_quest(c.QUEST_AREA_RADIUS)

        if hero.statistics.quests_done == 0:
            # since we get shortest path for first quest
            # and want to give exp as fast as can
            # and do not want to give more exp than level up required
            experience = int(experience / 2)

        place_experience_bonuses = {}
        person_experience_bonuses = {}

        for participant in self.quest_participants(quest_uid):

            fact = self.knowledge_base[participant.participant]

            if isinstance(fact, facts.Person):
                person = persons_storage.persons.get(fact.externals['id'])
                person_experience_bonuses[
                    person.id] = person.attrs.experience_bonus
                place = person.place
            elif isinstance(fact, facts.Place):
                place = places_storage.places.get(fact.externals['id'])

            place_experience_bonuses[place.id] = place.attrs.experience_bonus

        experience += experience * (sum(place_experience_bonuses.values()) +
                                    sum(person_experience_bonuses.values()))

        return experience

    def get_politic_power_for_quest(self, quest_uid, hero):
        base_politic_power = f.person_power_for_quest(c.QUEST_AREA_RADIUS)

        for participant in self.quest_participants(quest_uid):

            fact = self.knowledge_base[participant.participant]

            if isinstance(fact, facts.Person):
                person = persons_storage.persons.get(fact.externals['id'])
                base_politic_power += person.attrs.politic_power_bonus

        return base_politic_power

    ################################
    # general callbacks
    ################################

    def on_state__before_actions(self, state):

        if isinstance(state, facts.Start):
            self._start_quest(state, hero=self.hero)

    def on_state__after_actions(self, state):
        if isinstance(state, facts.Finish):
            self._finish_quest(state, hero=self.hero)

    def on_jump_start__before_actions(self, jump):
        pass

    def on_jump_start__after_actions(self, jump):
        pass

    def on_jump_end__before_actions(self, jump):
        pass

    def on_jump_end__after_actions(self, jump):
        if not isinstance(jump, facts.Option):
            return

        path = next((path
                     for path in self.knowledge_base.filter(facts.ChoicePath)
                     if path.option == jump.uid))

        used_markers = self.current_info.used_markers
        for marker in jump.markers:
            for markers_group in questgen_relations.OPTION_MARKERS_GROUPS:
                if marker not in markers_group:
                    continue

                for removed_marker in markers_group:
                    if removed_marker in used_markers:
                        del used_markers[removed_marker]

            used_markers[marker] = path.default

    ################################
    # do action callbacks
    ################################

    def do_message(self, action):
        self.current_info.process_message(self.knowledge_base, self.hero,
                                          action.type)

    def do_give_reward(self, action):
        self._give_reward(self.hero, action.type, action.scale)

    def do_fight(self, action):
        self._fight(action)

    def do_do_nothing(self, action):
        self._donothing(action.type)

    def do_upgrade_equipment(self, action):
        self._upgrade_equipment(self.current_info.process_message,
                                self.hero,
                                self.knowledge_base,
                                cost=action.cost)

    def do_move_near(self, action):
        if action.place:
            self._move_hero_near(destination=places_storage.places.get(
                self.knowledge_base[action.place].externals['id']),
                                 terrains=action.terrains)
        else:
            self._move_hero_near(destination=None, terrains=action.terrains)

    ################################
    # check requirements callbacks
    ################################

    def check_located_in(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage.places[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            person = persons_storage.persons[object_fact.externals['id']]
            return person.place.id == place.id

        if isinstance(object_fact, facts.Hero):
            return bool(self.hero.id == object_fact.externals['id']
                        and self.hero.position.place
                        and self.hero.position.place.id == place.id)

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage.places[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            # если городу принадлежит только одна клетка, нак которой он находится,
            # то прогулкой в его окрестностях считается и нахождение в нём самом
            if self.hero.position.place and len(
                    self.hero.position.place.nearest_cells) > 1:
                return False

            hero_place = self.hero.position.get_nearest_dominant_place()
            return place.id == hero_place.id

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if not isinstance(object_fact, facts.Hero):
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if self.hero.id != object_fact.externals['id']:
            return False

        place_from = places_storage.places[self.knowledge_base[
            requirement.place_from].externals['id']]
        place_to = places_storage.places[self.knowledge_base[
            requirement.place_to].externals['id']]
        percents = requirement.percents

        path_to_position_length = self.hero.position.get_minumum_distance_to(
            place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(
            place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        return current_percents >= percents

    def check_has_money(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False
            return self.hero.money >= requirement.money

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_is_alive(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return True

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            return self.hero.is_alive

        raise exceptions.UnknownRequirementError(requirement=requirement)

    ################################
    # satisfy requirements callbacks
    ################################

    def satisfy_located_in(self, requirement):
        from . import logic

        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            person_uid = object_fact.uid
            person = persons_storage.persons[object_fact.externals['id']]

            new_place_uid = uids.place(person.place.id)

            if new_place_uid not in self.knowledge_base:
                self.knowledge_base += logic.fact_place(person.place)

            # переписываем все ограничения в базе
            for fact in self.knowledge_base.filter(facts.State):
                for state_requirement in fact.require:
                    if isinstance(state_requirement, requirements.LocatedIn
                                  ) and state_requirement.object == person_uid:
                        state_requirement.place = new_place_uid

            return

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_to(destination=places_storage.places[
            self.knowledge_base[requirement.place].externals['id']])

    def satisfy_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if requirement.place is None:
            self._move_hero_near(destination=None,
                                 terrains=requirement.terrains)
        else:
            self._move_hero_near(destination=places_storage.places.get(
                self.knowledge_base[requirement.place].externals['id']),
                                 terrains=requirement.terrains)

    def satisfy_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_on_road(
            place_from=places_storage.places[self.knowledge_base[
                requirement.place_from].externals['id']],
            place_to=places_storage.places[self.knowledge_base[
                requirement.place_to].externals['id']],
            percents=requirement.percents)

    def satisfy_has_money(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)

    def satisfy_is_alive(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)

    ################################
    # ui info
    ################################

    def ui_info(self):
        return {
            'line': [info.ui_info(self.hero) for info in self.quests_stack]
        }

    @classmethod
    def no_quests_ui_info(cls, in_place):
        if in_place:
            return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]}
        else:
            return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]}

    @classmethod
    def next_spending_ui_info(cls, spending):
        NEXT_SPENDING_INFO = QuestInfo(type='next-spending',
                                       uid='next-spending',
                                       name='Накопить золото',
                                       action='копит',
                                       choice=None,
                                       choice_alternatives=(),
                                       experience=0,
                                       power=0,
                                       experience_bonus=0,
                                       power_bonus=0,
                                       actors={'goal': (spending, 'цель')},
                                       used_markers=set())
        return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
Exemplo n.º 7
0
class QuestPrototype(object):
    def __init__(self,
                 hero,
                 knowledge_base,
                 quests_stack=None,
                 created_at=None,
                 states_to_percents=None):
        self.hero = hero
        self.quests_stack = [] if quests_stack is None else quests_stack
        self.knowledge_base = knowledge_base
        self.machine = Machine(knowledge_base=knowledge_base, interpreter=self)
        self.created_at = datetime.datetime.now(
        ) if created_at is None else created_at
        self.states_to_percents = states_to_percents if states_to_percents is not None else {}

    @property
    def current_info(self):
        return self.quests_stack[-1]

    def serialize(self):
        return {
            'quests_stack': [info.serialize() for info in self.quests_stack],
            'knowledge_base': self.knowledge_base.serialize(short=True),
            'created_at': time.mktime(self.created_at.timetuple()),
            'states_to_percents': self.states_to_percents,
        }

    @classmethod
    def deserialize(cls, hero, data):
        return cls(
            knowledge_base=KnowledgeBase.deserialize(data['knowledge_base'],
                                                     fact_classes=facts.FACTS),
            quests_stack=[
                QuestInfo.deserialize(info_data)
                for info_data in data['quests_stack']
            ],
            created_at=datetime.datetime.fromtimestamp(data['created_at']),
            states_to_percents=data['states_to_percents'],
            hero=hero)

    @property
    def percents(self):
        return self.states_to_percents.get(self.machine.pointer.state, 0.0)

    @property
    def is_processed(self):
        return self.machine.is_processed

    def get_nearest_choice(self):
        return self.machine.get_nearest_choice()

    def make_choice(self, option_uid):
        choice, options, defaults = self.get_nearest_choice()

        if self.knowledge_base[option_uid].state_from != choice.uid:
            return False

        if not any(option.uid == option_uid for option in options):
            return False

        if not defaults[0].default:
            return False

        if not transformators.change_choice(knowledge_base=self.knowledge_base,
                                            new_option_uid=option_uid,
                                            default=False):
            return False

        self.machine.sync_pointer()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero,
                                           *self.get_nearest_choice())

        self.hero.actions.request_replane()
        return True

    ###########################################
    # Object operations
    ###########################################

    def process(self):
        self.hero.quests.mark_updated()

        step_result = self.machine.do_step()

        if self.quests_stack:
            self.current_info.sync_choices(self.knowledge_base, self.hero,
                                           *self.get_nearest_choice())

        if step_result:
            return self.percents

        return 1

    def _move_hero_to(self, destination, break_at=None):
        from the_tale.game.actions.prototypes import ActionMoveToPrototype

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        ActionMoveToPrototype.create(hero=self.hero,
                                     destination=destination,
                                     break_at=break_at)

    def _move_hero_near(self, destination, terrains=None, back=False):
        from the_tale.game.actions.prototypes import ActionMoveNearPlacePrototype

        if destination is None:
            destination = self.hero.position.get_nearest_dominant_place()

        ActionMoveNearPlacePrototype.create(hero=self.hero,
                                            place=destination,
                                            back=back,
                                            terrains=terrains)

    def _move_hero_on_road(self, place_from, place_to, percents):

        if self.hero.position.is_walking:
            self._move_hero_near(destination=None, back=True)
            return

        path_to_position_length = self.hero.position.get_minumum_distance_to(
            place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(
            place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        if percents <= current_percents:
            return

        path_to_pass = (percents - current_percents) * full_path_length

        self._move_hero_to(destination=place_to,
                           break_at=path_to_pass / path_from_position_length)

    def _give_power(self, hero, place, power):
        power = power * self.current_info.total_power

        if power > 0:
            hero.places_history.add_place(place.id)

        return power

    def _give_person_power(self, hero, person, power):

        power = self._give_power(hero, person.place, power)

        power, positive_bonus, negative_bonus = hero.modify_politics_power(
            power, person=person)

        person_uid = uids.person(person.id)
        has_profession_marker = [
            marker
            for marker in self.knowledge_base.filter(facts.ProfessionMarker)
            if marker.person == person_uid
        ]
        if has_profession_marker:
            power /= len(PERSON_TYPE.records)
            positive_bonus /= len(PERSON_TYPE.records)
            negative_bonus /= len(PERSON_TYPE.records)

        if not hero.can_change_person_power(person):
            return 0

        person.cmd_change_power(power, positive_bonus, negative_bonus)

        return power

    def _give_place_power(self, hero, place, power):

        power = self._give_power(hero, place, power)

        power, positive_bonus, negative_bonus = hero.modify_politics_power(
            power, place=place)

        if not hero.can_change_place_power(place):
            return 0

        place.cmd_change_power(power, positive_bonus, negative_bonus)

        return power

    def _fight(self, action):
        from the_tale.game.actions.prototypes import ActionBattlePvE1x1Prototype

        if action.mob is not None:
            mob = mobs_storage[self.knowledge_base[
                action.mob].externals['id']].create_mob(self.hero,
                                                        is_boss=True)
        else:
            mob = mobs_storage.get_random_mob(self.hero,
                                              mercenary=action.mercenary,
                                              is_boss=True)

            if mob is None:
                mobs_storage.get_random_mob(self.hero, is_boss=True)

        ActionBattlePvE1x1Prototype.create(hero=self.hero, mob=mob)

    def give_social_power(self, quest_results):
        results = {}

        for person_uid, result in quest_results.iteritems():
            person_id = self.knowledge_base[person_uid].externals['id']
            if person_id not in persons_storage.persons_storage:
                continue
            results[persons_storage.persons_storage[person_id]] = result

        VALUABLE_RESULTS = (QUEST_RESULTS.SUCCESSED, QUEST_RESULTS.FAILED)

        for (person_1,
             quest_1_result), (person_2,
                               quest_2_result) in itertools.combinations(
                                   results.iteritems(), 2):

            if quest_1_result not in VALUABLE_RESULTS or quest_2_result not in VALUABLE_RESULTS:
                continue

            connection_type = persons_storage.social_connections.get_connection_type(
                person_1, person_2)

            if connection_type is None:
                continue

            if (not ((connection_type.is_PARTNER
                      and quest_1_result == quest_2_result) or
                     (connection_type.is_CONCURRENT
                      and quest_1_result != quest_2_result))):
                continue

            self._give_person_power(
                hero=self.hero,
                person=person_1,
                power=1 if quest_1_result == QUEST_RESULTS.SUCCESSED else -1)

            self._give_person_power(
                hero=self.hero,
                person=person_2,
                power=1 if quest_2_result == QUEST_RESULTS.SUCCESSED else -1)

    def _finish_quest(self, finish, hero):

        experience = self.modify_experience(self.current_info.experience)
        experience_bonus = self.modify_experience(
            self.current_info.experience_bonus)

        hero.add_experience(experience)
        hero.add_experience(experience_bonus, without_modifications=True)

        hero.statistics.change_quests_done(1)

        if hero.companion:
            hero.companion.add_experience(c.COMPANIONS_COHERENCE_EXP_PER_QUEST)

        for person_uid, result in finish.results.iteritems():
            if result == QUEST_RESULTS.FAILED:
                hero.quests.add_interfered_person(
                    self.knowledge_base[person_uid].externals['id'])

        for marker, default in self.current_info.used_markers.iteritems():
            for change_source in HABIT_CHANGE_SOURCE.records:
                if change_source.quest_marker == marker and change_source.quest_default == default:
                    self.hero.update_habits(change_source)

        self.give_social_power(finish.results)

        self.quests_stack.pop()

    def _give_reward(self, hero, reward_type, scale):

        quest_info = self.current_info

        scale = quest_info.get_real_reward_scale(hero, scale)

        if hero.can_get_artifact_for_quest():

            level_delta = int(math.ceil(abs(scale)))
            if scale < 0:
                level_delta = -level_delta

            artifact, unequipped, sell_price = self.hero.receive_artifact(
                equip=False,
                better=False,
                prefered_slot=False,
                prefered_item=False,
                archetype=False,
                level_delta=level_delta)

            if artifact is not None:
                quest_info.process_message(
                    knowledge_base=self.knowledge_base,
                    hero=self.hero,
                    message='%s_artifact' % reward_type,
                    ext_substitution={'artifact': artifact})
                return

        money = int(max(1, f.sell_artifact_price(hero.level) * scale))

        hero.change_money(MONEY_SOURCE.EARNED_FROM_QUESTS, money)

        quest_info.process_message(knowledge_base=self.knowledge_base,
                                   hero=self.hero,
                                   message='%s_money' % reward_type,
                                   ext_substitution={'coins': money})

    def _donothing(self, donothing_type):
        from the_tale.game.actions.prototypes import ActionDoNothingPrototype

        donothing = relations.DONOTHING_TYPE.index_value[donothing_type]

        writer = writers.get_writer(hero=self.hero,
                                    type=self.current_info.type,
                                    message=donothing_type,
                                    substitution={})

        ActionDoNothingPrototype.create(
            hero=self.hero,
            duration=donothing.duration,
            messages_prefix=writer.journal_id(),
            messages_probability=donothing.messages_probability)

    @classmethod
    def _get_upgrdade_choice(cls, hero):
        choices = [relations.UPGRADE_EQUIPMENT_VARIANTS.BUY]

        if hero.preferences.equipment_slot and hero.equipment.get(
                hero.preferences.equipment_slot) is not None:
            choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.SHARP)

            if hero.equipment.get(
                    hero.preferences.equipment_slot).can_be_broken():
                choices.append(relations.UPGRADE_EQUIPMENT_VARIANTS.REPAIR)

        return random.choice(choices)

    @classmethod
    def upgrade_equipment_cost(cls, hero_info):
        return int(
            f.normal_action_price(hero_info.level) *
            ITEMS_OF_EXPENDITURE.get_quest_upgrade_equipment_fraction())

    @classmethod
    def _upgrade_equipment(cls, process_message, hero, knowledge_base, cost):

        if cost is not None:
            cost = min(cost, hero.money)

        upgrade_choice = cls._get_upgrdade_choice(hero)

        if upgrade_choice.is_BUY:
            artifact, unequipped, sell_price = hero.receive_artifact(
                equip=True,
                better=True,
                prefered_slot=True,
                prefered_item=True,
                archetype=True)

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_ARTIFACTS, -cost)
                if artifact is None:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__fail',
                                    ext_substitution={'coins': cost})
                elif unequipped:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__buy_and_change',
                                    ext_substitution={
                                        'coins': cost,
                                        'artifact': artifact,
                                        'unequipped': unequipped,
                                        'sell_price': sell_price
                                    })
                else:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade__buy',
                                    ext_substitution={
                                        'coins': cost,
                                        'artifact': artifact
                                    })
            else:
                if artifact is None:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__fail',
                                    ext_substitution={})
                elif unequipped:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__buy_and_change',
                                    ext_substitution={
                                        'artifact': artifact,
                                        'unequipped': unequipped,
                                        'sell_price': sell_price
                                    })
                else:
                    process_message(knowledge_base,
                                    hero,
                                    message='upgrade_free__buy',
                                    ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_SHARP:
            artifact = hero.sharp_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_SHARPENING, -cost)
                process_message(knowledge_base,
                                hero,
                                message='upgrade__sharp',
                                ext_substitution={
                                    'coins': cost,
                                    'artifact': artifact
                                })
            else:
                process_message(knowledge_base,
                                hero,
                                message='upgrade_free__sharp',
                                ext_substitution={'artifact': artifact})

        elif upgrade_choice.is_REPAIR:
            artifact = hero.repair_artifact()

            if cost is not None:
                hero.change_money(MONEY_SOURCE.SPEND_FOR_REPAIRING, -cost)
                process_message(knowledge_base,
                                hero,
                                message='upgrade__repair',
                                ext_substitution={
                                    'coins': cost,
                                    'artifact': artifact
                                })
            else:
                process_message(knowledge_base,
                                hero,
                                message='upgrade_free__repair',
                                ext_substitution={'artifact': artifact})

        else:
            raise exceptions.UnknownUpgadeEquipmentTypeError(
                type=upgrade_choice)

    def _start_quest(self, start, hero):
        hero.quests.update_history(start.type,
                                   TimePrototype.get_current_turn_number())
        self.quests_stack.append(
            QuestInfo.construct(type=start.type,
                                uid=start.uid,
                                knowledge_base=self.machine.knowledge_base,
                                hero=hero))

    def modify_experience(self, experience):
        quest_uid = self.current_info.uid

        experience_modifiers = {}

        for participant in self.knowledge_base.filter(facts.QuestParticipant):
            if quest_uid != participant.start:
                continue
            fact = self.knowledge_base[participant.participant]
            if isinstance(fact, facts.Person):
                place = persons_storage.persons_storage.get(
                    fact.externals['id']).place
            elif isinstance(fact, facts.Place):
                place = places_storage.get(fact.externals['id'])

            experience_modifiers[place.id] = place.get_experience_modifier()

        experience += experience * sum(experience_modifiers.values())
        return experience

    ################################
    # general callbacks
    ################################

    def on_state__before_actions(self, state):

        if isinstance(state, facts.Start):
            self._start_quest(state, hero=self.hero)

    def on_state__after_actions(self, state):
        if isinstance(state, facts.Finish):
            self._finish_quest(state, hero=self.hero)

    def on_jump_start__before_actions(self, jump):
        pass

    def on_jump_start__after_actions(self, jump):
        pass

    def on_jump_end__before_actions(self, jump):
        pass

    def on_jump_end__after_actions(self, jump):
        if not isinstance(jump, facts.Option):
            return

        path = (path for path in self.knowledge_base.filter(facts.ChoicePath)
                if path.option == jump.uid).next()

        used_markers = self.current_info.used_markers
        for marker in jump.markers:
            for markers_group in questgen_relations.OPTION_MARKERS_GROUPS:
                if marker not in markers_group:
                    continue

                for removed_marker in markers_group:
                    if removed_marker in used_markers:
                        del used_markers[removed_marker]

            used_markers[marker] = path.default

    ################################
    # do action callbacks
    ################################

    def do_message(self, action):
        self.current_info.process_message(self.knowledge_base, self.hero,
                                          action.type)

    def do_give_power(self, action):
        # every quest branch give equal power to its participants
        power = 0

        if action.power > 0:
            power = 1

        if action.power < 0:
            power = -1

        recipient = self.knowledge_base[action.object]
        if isinstance(recipient, facts.Person):
            self._give_person_power(
                self.hero,
                persons_storage.persons_storage[recipient.externals['id']],
                power)
        elif isinstance(recipient, facts.Place):
            self._give_place_power(self.hero,
                                   places_storage[recipient.externals['id']],
                                   power)
        else:
            raise exceptions.UnknownPowerRecipientError(recipient=recipient)

    def do_give_reward(self, action):
        self._give_reward(self.hero, action.type, action.scale)

    def do_fight(self, action):
        self._fight(action)

    def do_do_nothing(self, action):
        self._donothing(action.type)

    def do_upgrade_equipment(self, action):
        self._upgrade_equipment(self.current_info.process_message,
                                self.hero,
                                self.knowledge_base,
                                cost=action.cost)

    def do_move_near(self, action):
        if action.place:
            self._move_hero_near(destination=places_storage.get(
                self.knowledge_base[action.place].externals['id']),
                                 terrains=action.terrains)
        else:
            self._move_hero_near(destination=None, terrains=action.terrains)

    ################################
    # check requirements callbacks
    ################################

    def check_located_in(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            person = persons_storage.persons_storage[
                object_fact.externals['id']]
            return person.place.id == place.id

        if isinstance(object_fact, facts.Hero):
            return bool(self.hero.id == object_fact.externals['id']
                        and self.hero.position.place
                        and self.hero.position.place.id == place.id)

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]
        place_fact = self.knowledge_base[requirement.place]

        place = places_storage[place_fact.externals['id']]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            if self.hero.position.place:
                return False

            hero_place = self.hero.position.get_nearest_dominant_place()
            return place.id == hero_place.id

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if not isinstance(object_fact, facts.Hero):
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if self.hero.id != object_fact.externals['id']:
            return False

        place_from = places_storage[self.knowledge_base[
            requirement.place_from].externals['id']]
        place_to = places_storage[self.knowledge_base[
            requirement.place_to].externals['id']]
        percents = requirement.percents

        path_to_position_length = self.hero.position.get_minumum_distance_to(
            place_from)
        path_from_position_length = self.hero.position.get_minumum_distance_to(
            place_to)

        full_path_length = path_to_position_length + path_from_position_length

        if path_to_position_length < E:
            current_percents = 0.0
        elif path_from_position_length < E:
            current_percents = 1.0
        else:
            current_percents = path_to_position_length / full_path_length

        return current_percents >= percents

    def check_has_money(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return False

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False
            return self.hero.money >= requirement.money

        raise exceptions.UnknownRequirementError(requirement=requirement)

    def check_is_alive(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if isinstance(object_fact, facts.Person):
            return True

        if isinstance(object_fact, facts.Hero):
            if self.hero.id != object_fact.externals['id']:
                return False

            return self.hero.is_alive

        raise exceptions.UnknownRequirementError(requirement=requirement)

    ################################
    # satisfy requirements callbacks
    ################################

    def satisfy_located_in(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_to(destination=places_storage[self.knowledge_base[
            requirement.place].externals['id']])

    def satisfy_located_near(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        if requirement.place is None:
            self._move_hero_near(destination=None,
                                 terrains=requirement.terrains)
        else:
            self._move_hero_near(destination=places_storage.get(
                self.knowledge_base[requirement.place].externals['id']),
                                 terrains=requirement.terrains)

    def satisfy_located_on_road(self, requirement):
        object_fact = self.knowledge_base[requirement.object]

        if not isinstance(
                object_fact,
                facts.Hero) or self.hero.id != object_fact.externals['id']:
            raise exceptions.UnknownRequirementError(requirement=requirement)

        self._move_hero_on_road(place_from=places_storage[self.knowledge_base[
            requirement.place_from].externals['id']],
                                place_to=places_storage[self.knowledge_base[
                                    requirement.place_to].externals['id']],
                                percents=requirement.percents)

    def satisfy_has_money(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)

    def satisfy_is_alive(self, requirement):
        raise exceptions.UnknownRequirementError(requirement=requirement)

    ################################
    # ui info
    ################################

    def ui_info(self):
        return {
            'line': [info.ui_info(self.hero) for info in self.quests_stack]
        }

    @classmethod
    def no_quests_ui_info(cls, in_place):
        if in_place:
            return {'line': [NO_QUEST_INFO__IN_PLACE.ui_info(None)]}
        else:
            return {'line': [NO_QUEST_INFO__OUT_PLACE.ui_info(None)]}

    @classmethod
    def next_spending_ui_info(cls, spending):
        NEXT_SPENDING_INFO = QuestInfo(type='next-spending',
                                       uid='next-spending',
                                       name=u'Накопить золото',
                                       action=u'копит',
                                       choice=None,
                                       choice_alternatives=(),
                                       experience=0,
                                       power=0,
                                       experience_bonus=0,
                                       power_bonus=0,
                                       actors={'goal': (spending, u'цель')},
                                       used_markers=set())
        return {'line': [NEXT_SPENDING_INFO.ui_info(None)]}
Exemplo n.º 8
0
class MachineTests(unittest.TestCase):

    def setUp(self):
        self.kb = KnowledgeBase()

        self.hero = facts.Hero(uid='hero')

        self.start = facts.Start(uid='start', type='test', nesting=0)
        self.state_1 = facts.State(uid='state_1')
        self.state_2 = facts.State(uid='state_2')
        self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0)

        self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero]

        self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter())

    def test_get_pointer(self):
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
        pointer = self.machine.pointer
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [pointer])

    def test_get_available_jumps__no_jumps(self):
        self.assertEqual(self.machine.get_available_jumps(self.start), [])

    def test_get_available_jumps__all_jumps(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.assertEqual(set(jump.uid for jump in self.machine.get_available_jumps(self.start)),
                         set([jump_1.uid, jump_2.uid]))

    def test_get_available_jumps__for_choice_state(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice, option_1, option_2, path)

        for i in range(100):
            self.assertEqual(self.machine.get_available_jumps(choice), [option_2])

    def test_get_available_jumps__for_question_state(self):
        condition = requirements.LocatedIn(object='object', place='place')

        question = facts.Question(uid='choice', condition=[condition])
        answer_1 = facts.Answer(state_from=question.uid, state_to=self.state_1.uid, condition=True)
        answer_2 = facts.Answer(state_from=question.uid, state_to=self.state_2.uid, condition=False)
        self.kb += (question, answer_1, answer_2)

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=False)):
            for i in range(100):
                self.assertEqual(self.machine.get_available_jumps(question), [answer_2])

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=True)):
            for i in range(100):
                self.assertEqual(self.machine.get_available_jumps(question), [answer_1])

    def test_get_next_jump__no_jumps(self):
        self.assertRaises(exceptions.NoJumpsAvailableError,
                          self.machine.get_next_jump, self.start)

    def test_get_next_jump__all_jumps(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        jumps = set()

        for i in range(100):
            jumps.add(self.machine.get_next_jump(self.start, single=False).uid)

        self.assertEqual(jumps, set([jump_1.uid, jump_2.uid]))

    def test_get_next_jump__require_one_jump__exception(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.assertRaises(exceptions.MoreThenOneJumpsAvailableError, self.machine.get_next_jump, self.start, single=True)

    def test_get_next_jump__require_one_jump(self):
        jump = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        self.kb += jump
        self.assertEqual(self.machine.get_next_jump(self.start, single=True).uid, jump.uid)

    def test_can_do_step__no_pointer(self):
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
        self.assertTrue(self.machine.can_do_step())

    @mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=False))
    def test_can_do_step__requirement_failed(self):
        state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')])
        jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid)
        self.kb += [ state_3, jump_3]

        pointer = self.machine.pointer
        self.kb -= pointer
        self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid)

        self.assertFalse(self.machine.can_do_step())

    def test_can_do_step__success(self):
        state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')])
        jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid)
        self.kb += [ state_3, jump_3]

        pointer = self.machine.pointer
        self.kb -= pointer
        self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid)

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=True)):
            self.assertTrue(self.machine.can_do_step())


    def test_do_step__no_pointer(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
                                self.machine.step()
                                self.assertEqual(len(list(self.kb.filter(facts.Pointer))), 1)

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_state__before_actions(state=self.start),
                                                                            mock.call.on_state__after_actions(state=self.start)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, None)


    def test_do_step__finish(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.kb += jump_1

        self.machine.step()
        self.machine.step()

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_end__before_actions(jump=jump_1),
                                                                            mock.call.on_jump_end__after_actions(jump=jump_1),
                                                                            mock.call.on_state__before_actions(state=self.finish_1),
                                                                            mock.call.on_state__after_actions(state=self.finish_1)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.finish_1.uid)
        self.assertEqual(pointer.jump, None)

    def test_do_step__step_after_finish(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.kb += jump_1

        self.machine.step()
        self.machine.step()
        self.machine.step()

        self.assertRaises(exceptions.NoJumpsFromLastStateError, self.machine.step)

    def test_do_step__next_jump(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.machine.step()

        calls_manager = mock.MagicMock()

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, None)

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_1),
                                                                            mock.call.on_jump_start__after_actions(jump=jump_1)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, jump_1.uid)

    def test_do_step__next_state(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.machine.step()
        self.machine.step()
        self.machine.step()

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.state_1.uid)
        self.assertEqual(pointer.jump, None)

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_2),
                                                                            mock.call.on_jump_start__after_actions(jump=jump_2)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.state_1.uid)
        self.assertEqual(pointer.jump, jump_2.uid)

    def test_sync_pointer(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        pointer = facts.Pointer(state=choice.uid, jump=option_1.uid)

        self.kb += (choice, option_1, option_2, path, pointer)

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.sync_pointer()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=option_2),
                                                                            mock.call.on_jump_start__after_actions(jump=option_2)])

        self.assertEqual(self.machine.pointer.jump, option_2.uid)
        self.assertEqual(self.machine.pointer.state, choice.uid)

    def test_get_start_state(self):
        start_2 = facts.Start(uid='s2', type='test', nesting=0)
        start_3 = facts.Start(uid='start_3', type='test', nesting=0)


        self.kb += [ start_2,
                     start_3,
                     facts.Jump(state_from=self.start.uid, state_to=start_2.uid),
                     facts.Jump(state_from=start_3.uid, state_to=self.start.uid) ]

        self.assertEqual(self.machine.get_start_state().uid, start_3.uid)

    def test_get_nearest_choice__no_choice(self):
        self.kb += facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    facts.Jump(state_from=self.start.uid, state_to=choice.uid)    )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_1.uid, option_2.uid]))
        self.assertEqual([p.uid for p in path_], [path.uid])

    def test_get_nearest_choice__2_choices(self):
        choice_1 = facts.Choice(uid='choice_1')

        choice_2 = facts.Choice(uid='choice_2')
        option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=())
        option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=())
        path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True)

        option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=())
        option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=())
        path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True)

        self.kb += (choice_1,
                    option_1_1,
                    option_1_2,
                    path_1,

                    choice_2,
                    option_2_1,
                    option_2_2,
                    path_2,
                    facts.Jump(state_from=self.start.uid, state_to=choice_1.uid),
            )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice_1.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_1_1.uid, option_1_2.uid]))
        self.assertEqual([p.uid for p in path_], [path_1.uid])

    def test_get_nearest_choice__2_choices__second_choice(self):
        choice_1 = facts.Choice(uid='choice_1')

        choice_2 = facts.Choice(uid='choice_2')
        option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=())
        option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=())
        path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True)

        option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=())
        option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=())
        path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True)

        self.kb += (choice_1,
                    option_1_1,
                    option_1_2,
                    path_1,

                    choice_2,
                    option_2_1,
                    option_2_2,
                    path_2,
                    facts.Jump(state_from=self.start.uid, state_to=choice_1.uid),

                    facts.Pointer(state=choice_2.uid)
            )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice_2.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_2_1.uid, option_2_2.uid]))
        self.assertEqual([p.uid for p in path_], [path_2.uid])

    def test_get_nearest_choice__choice_after_finish(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid),
                    facts.Jump(state_from=self.finish_1.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice_after_question(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)

        question = facts.Question(uid='question', condition=())

        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    question,
                    facts.Jump(state_from=self.start.uid, state_to=question.uid),
                    facts.Jump(state_from=question.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice_after_start(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        start_2 = facts.Start(uid='start_2', type='test', nesting=0)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    start_2,
                    facts.Jump(state_from=self.start.uid, state_to=start_2.uid),
                    facts.Jump(state_from=start_2.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))


    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: True)
    def test_do_step__step_done(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            self.assertTrue(self.machine.do_step())

        self.assertEqual(step.call_args_list, [mock.call()])

    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False)
    @mock.patch('questgen.machine.Machine.is_processed', True)
    def test_do_step__quest_processed(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            self.assertFalse(self.machine.do_step())

        self.assertEqual(step.call_args_list, [])

    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False)
    @mock.patch('questgen.machine.Machine.is_processed', False)
    @mock.patch('questgen.machine.Machine.next_state', 'next-state')
    def test_do_step__satisfy_requirements(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            with mock.patch('questgen.machine.Machine.satisfy_requirements') as satisfy_requirements:
                self.assertTrue(self.machine.do_step())

        self.assertEqual(step.call_args_list, [])
        self.assertEqual(satisfy_requirements.call_args_list, [mock.call('next-state')])
Exemplo n.º 9
0
class MachineTests(unittest.TestCase):

    def setUp(self):
        self.kb = KnowledgeBase()

        self.hero = facts.Hero(uid='hero')

        self.start = facts.Start(uid='start', type='test', nesting=0)
        self.state_1 = facts.State(uid='state_1')
        self.state_2 = facts.State(uid='state_2')
        self.finish_1 = facts.Finish(start='start', uid='finish_1', results={}, nesting=0)

        self.kb += [ self.start, self.state_1, self.state_2, self.finish_1, self.hero]

        self.machine = Machine(knowledge_base=self.kb, interpreter=FakeInterpreter())

    def test_get_pointer(self):
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
        pointer = self.machine.pointer
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [pointer])

    def test_get_available_jumps__no_jumps(self):
        self.assertEqual(self.machine.get_available_jumps(self.start), [])

    def test_get_available_jumps__all_jumps(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.assertEqual(set(jump.uid for jump in self.machine.get_available_jumps(self.start)),
                         set([jump_1.uid, jump_2.uid]))

    def test_get_available_jumps__for_choice_state(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice, option_1, option_2, path)

        for i in xrange(100):
            self.assertEqual(self.machine.get_available_jumps(choice), [option_2])

    def test_get_available_jumps__for_question_state(self):
        condition = requirements.LocatedIn(object='object', place='place')

        question = facts.Question(uid='choice', condition=[condition])
        answer_1 = facts.Answer(state_from=question.uid, state_to=self.state_1.uid, condition=True)
        answer_2 = facts.Answer(state_from=question.uid, state_to=self.state_2.uid, condition=False)
        self.kb += (question, answer_1, answer_2)

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=False)):
            for i in xrange(100):
                self.assertEqual(self.machine.get_available_jumps(question), [answer_2])

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_located_in=True)):
            for i in xrange(100):
                self.assertEqual(self.machine.get_available_jumps(question), [answer_1])

    def test_get_next_jump__no_jumps(self):
        self.assertRaises(exceptions.NoJumpsAvailableError,
                          self.machine.get_next_jump, self.start)

    def test_get_next_jump__all_jumps(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        jumps = set()

        for i in xrange(100):
            jumps.add(self.machine.get_next_jump(self.start, single=False).uid)

        self.assertEqual(jumps, set([jump_1.uid, jump_2.uid]))

    def test_get_next_jump__require_one_jump__exception(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.start.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.assertRaises(exceptions.MoreThenOneJumpsAvailableError, self.machine.get_next_jump, self.start, single=True)

    def test_get_next_jump__require_one_jump(self):
        jump = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        self.kb += jump
        self.assertEqual(self.machine.get_next_jump(self.start, single=True).uid, jump.uid)

    def test_can_do_step__no_pointer(self):
        self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
        self.assertTrue(self.machine.can_do_step())

    @mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=False))
    def test_can_do_step__requirement_failed(self):
        state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')])
        jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid)
        self.kb += [ state_3, jump_3]

        pointer = self.machine.pointer
        self.kb -= pointer
        self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid)

        self.assertFalse(self.machine.can_do_step())

    def test_can_do_step__success(self):
        state_3 = facts.State(uid='state_3', require=[requirements.IsAlive(object='hero')])
        jump_3 = facts.Jump(state_from=self.start.uid, state_to=state_3.uid)
        self.kb += [ state_3, jump_3]

        pointer = self.machine.pointer
        self.kb -= pointer
        self.kb += pointer.change(state=self.start.uid, jump=jump_3.uid)

        with mock.patch('questgen.machine.Machine.interpreter', FakeInterpreter(check_is_alive=True)):
            self.assertTrue(self.machine.can_do_step())


    def test_do_step__no_pointer(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.assertEqual(list(self.kb.filter(facts.Pointer)), [])
                                self.machine.step()
                                self.assertEqual(len(list(self.kb.filter(facts.Pointer))), 1)

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_state__before_actions(state=self.start),
                                                                            mock.call.on_state__after_actions(state=self.start)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, None)


    def test_do_step__finish(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.kb += jump_1

        self.machine.step()
        self.machine.step()

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_end__before_actions(jump=jump_1),
                                                                            mock.call.on_jump_end__after_actions(jump=jump_1),
                                                                            mock.call.on_state__before_actions(state=self.finish_1),
                                                                            mock.call.on_state__after_actions(state=self.finish_1)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.finish_1.uid)
        self.assertEqual(pointer.jump, None)

    def test_do_step__step_after_finish(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.kb += jump_1

        self.machine.step()
        self.machine.step()
        self.machine.step()

        self.assertRaises(exceptions.NoJumpsFromLastStateError, self.machine.step)

    def test_do_step__next_jump(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.machine.step()

        calls_manager = mock.MagicMock()

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, None)

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_1),
                                                                            mock.call.on_jump_start__after_actions(jump=jump_1)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.start.uid)
        self.assertEqual(pointer.jump, jump_1.uid)

    def test_do_step__next_state(self):
        jump_1 = facts.Jump(state_from=self.start.uid, state_to=self.state_1.uid)
        jump_2 = facts.Jump(state_from=self.state_1.uid, state_to=self.state_2.uid)
        self.kb += [jump_1, jump_2]

        self.machine.step()
        self.machine.step()
        self.machine.step()

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.state_1.uid)
        self.assertEqual(pointer.jump, None)

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.step()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=jump_2),
                                                                            mock.call.on_jump_start__after_actions(jump=jump_2)])

        pointer = self.machine.pointer
        self.assertEqual(pointer.state, self.state_1.uid)
        self.assertEqual(pointer.jump, jump_2.uid)

    def test_sync_pointer(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        pointer = facts.Pointer(state=choice.uid, jump=option_1.uid)

        self.kb += (choice, option_1, option_2, path, pointer)

        calls_manager = mock.MagicMock()

        with mock.patch.object(self.machine.interpreter, 'on_state__before_actions') as on_state__before_actions:
            with mock.patch.object(self.machine.interpreter, 'on_state__after_actions') as on_state__after_actions:
                with mock.patch.object(self.machine.interpreter, 'on_jump_start__before_actions') as on_jump_start__before_actions:
                    with mock.patch.object(self.machine.interpreter, 'on_jump_start__after_actions') as on_jump_start__after_actions:
                        with mock.patch.object(self.machine.interpreter, 'on_jump_end__before_actions') as on_jump_end__before_actions:
                            with mock.patch.object(self.machine.interpreter, 'on_jump_end__after_actions') as on_jump_end__after_actions:

                                calls_manager.attach_mock(on_state__before_actions, 'on_state__before_actions')
                                calls_manager.attach_mock(on_state__after_actions, 'on_state__after_actions')
                                calls_manager.attach_mock(on_jump_start__before_actions, 'on_jump_start__before_actions')
                                calls_manager.attach_mock(on_jump_start__after_actions, 'on_jump_start__after_actions')
                                calls_manager.attach_mock(on_jump_end__before_actions, 'on_jump_end__before_actions')
                                calls_manager.attach_mock(on_jump_end__after_actions, 'on_jump_end__after_actions')

                                self.machine.sync_pointer()

                                self.assertEqual(calls_manager.mock_calls, [mock.call.on_jump_start__before_actions(jump=option_2),
                                                                            mock.call.on_jump_start__after_actions(jump=option_2)])

        self.assertEqual(self.machine.pointer.jump, option_2.uid)
        self.assertEqual(self.machine.pointer.state, choice.uid)

    def test_get_start_state(self):
        start_2 = facts.Start(uid='s2', type='test', nesting=0)
        start_3 = facts.Start(uid='start_3', type='test', nesting=0)


        self.kb += [ start_2,
                     start_3,
                     facts.Jump(state_from=self.start.uid, state_to=start_2.uid),
                     facts.Jump(state_from=start_3.uid, state_to=self.start.uid) ]

        self.assertEqual(self.machine.get_start_state().uid, start_3.uid)

    def test_get_nearest_choice__no_choice(self):
        self.kb += facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid)
        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    facts.Jump(state_from=self.start.uid, state_to=choice.uid)    )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_1.uid, option_2.uid]))
        self.assertEqual([p.uid for p in path_], [path.uid])

    def test_get_nearest_choice__2_choices(self):
        choice_1 = facts.Choice(uid='choice_1')

        choice_2 = facts.Choice(uid='choice_2')
        option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=())
        option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=())
        path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True)

        option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=())
        option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=())
        path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True)

        self.kb += (choice_1,
                    option_1_1,
                    option_1_2,
                    path_1,

                    choice_2,
                    option_2_1,
                    option_2_2,
                    path_2,
                    facts.Jump(state_from=self.start.uid, state_to=choice_1.uid),
            )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice_1.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_1_1.uid, option_1_2.uid]))
        self.assertEqual([p.uid for p in path_], [path_1.uid])

    def test_get_nearest_choice__2_choices__second_choice(self):
        choice_1 = facts.Choice(uid='choice_1')

        choice_2 = facts.Choice(uid='choice_2')
        option_2_1 = facts.Option(state_from=choice_2.uid, state_to=self.state_1.uid, type='opt_2_1', markers=())
        option_2_2 = facts.Option(state_from=choice_2.uid, state_to=self.state_2.uid, type='opt_2_2', markers=())
        path_2 = facts.ChoicePath(choice=choice_2.uid, option=option_2_2.uid, default=True)

        option_1_1 = facts.Option(state_from=choice_1.uid, state_to=self.state_1.uid, type='opt_1_1', markers=())
        option_1_2 = facts.Option(state_from=choice_1.uid, state_to=choice_2.uid, type='opt_1_2', markers=())
        path_1 = facts.ChoicePath(choice=choice_1.uid, option=option_1_2.uid, default=True)

        self.kb += (choice_1,
                    option_1_1,
                    option_1_2,
                    path_1,

                    choice_2,
                    option_2_1,
                    option_2_2,
                    path_2,
                    facts.Jump(state_from=self.start.uid, state_to=choice_1.uid),

                    facts.Pointer(state=choice_2.uid)
            )

        choice_, options_, path_ = self.machine.get_nearest_choice()
        self.assertEqual(choice_.uid, choice_2.uid)
        self.assertEqual(set(o.uid for o in options_), set([option_2_1.uid, option_2_2.uid]))
        self.assertEqual([p.uid for p in path_], [path_2.uid])

    def test_get_nearest_choice__choice_after_finish(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    facts.Jump(state_from=self.start.uid, state_to=self.finish_1.uid),
                    facts.Jump(state_from=self.finish_1.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice_after_question(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)

        question = facts.Question(uid='question', condition=())

        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    question,
                    facts.Jump(state_from=self.start.uid, state_to=question.uid),
                    facts.Jump(state_from=question.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))

    def test_get_nearest_choice__choice_after_start(self):
        choice = facts.Choice(uid='choice')
        option_1 = facts.Option(state_from=choice.uid, state_to=self.state_1.uid, type='opt_1', markers=())
        option_2 = facts.Option(state_from=choice.uid, state_to=self.state_2.uid, type='opt_2', markers=())
        path = facts.ChoicePath(choice=choice.uid, option=option_2.uid, default=True)
        start_2 = facts.Start(uid='start_2', type='test', nesting=0)
        self.kb += (choice,
                    option_1,
                    option_2,
                    path,
                    start_2,
                    facts.Jump(state_from=self.start.uid, state_to=start_2.uid),
                    facts.Jump(state_from=start_2.uid, state_to=choice.uid))

        self.assertEqual(self.machine.get_nearest_choice(), (None, None, None))


    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: True)
    def test_do_step__step_done(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            self.assertTrue(self.machine.do_step())

        self.assertEqual(step.call_args_list, [mock.call()])

    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False)
    @mock.patch('questgen.machine.Machine.is_processed', True)
    def test_do_step__quest_processed(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            self.assertFalse(self.machine.do_step())

        self.assertEqual(step.call_args_list, [])

    @mock.patch('questgen.machine.Machine.can_do_step', lambda self: False)
    @mock.patch('questgen.machine.Machine.is_processed', False)
    @mock.patch('questgen.machine.Machine.next_state', 'next-state')
    def test_do_step__satisfy_requirements(self):
        with mock.patch('questgen.machine.Machine.step') as step:
            with mock.patch('questgen.machine.Machine.satisfy_requirements') as satisfy_requirements:
                self.assertTrue(self.machine.do_step())

        self.assertEqual(step.call_args_list, [])
        self.assertEqual(satisfy_requirements.call_args_list, [mock.call('next-state')])