Exemple #1
0
    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])
Exemple #2
0
    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)
Exemple #3
0
class Machine(object):
    POINTER_UID = facts.Pointer().uid

    __slots__ = ('knowledge_base', 'interpreter', 'unsatisfied_requirements')

    def __init__(self, knowledge_base, interpreter):
        self.knowledge_base = knowledge_base
        self.interpreter = interpreter
        self.unsatisfied_requirements = []

    @property
    def pointer(self):
        if self.POINTER_UID not in self.knowledge_base:
            self.knowledge_base += facts.Pointer()
        return self.knowledge_base[self.POINTER_UID]

    def _has_jumps(self, fact):
        return bool([jump for jump in self.knowledge_base.filter(facts.Jump) if jump.state_from == fact.uid])

    @property
    def is_processed(self): # TODO: tests
        finish = self.current_state

        if not isinstance(finish, facts.Finish):
            return False

        return finish.is_external

    @property
    def current_state(self):
        if self.pointer.state is None:
            return None
        return self.knowledge_base[self.pointer.state]

    def get_start_state(self):
        jump_ends = set((jump.state_to for jump in self.knowledge_base.filter(facts.Jump)))
        return next((start for start in self.knowledge_base.filter(facts.Start) if start.uid not in jump_ends))

    @property
    def next_state(self):
        if self.current_state is None:
            return self.get_start_state()

        if self.pointer.jump is None:
            return None

        return self.knowledge_base[self.knowledge_base[self.pointer.jump].state_to]

    def step(self):
        next_state = self.next_state

        if next_state:
            new_pointer = self.pointer.change(state=next_state.uid, jump=None)

            if self.pointer.jump is not None:
                next_jump = self.knowledge_base[self.pointer.jump]
                self.interpreter.on_jump_end__before_actions(jump=next_jump)
                self.do_actions(next_jump.end_actions)
                self.interpreter.on_jump_end__after_actions(jump=next_jump)

            self.interpreter.on_state__before_actions(state=next_state)
            self.do_actions(next_state.actions)
            self.interpreter.on_state__after_actions(state=next_state)
        else:
            next_jump = None

            if not self._has_jumps(self.current_state):
                raise exceptions.NoJumpsFromLastStateError(state=self.current_state)

            next_jump = self.get_next_jump(self.current_state)

            new_pointer = self.pointer.change(jump=next_jump.uid if next_jump else None)

            if next_jump is not None:
                self.interpreter.on_jump_start__before_actions(jump=next_jump)
                self.do_actions(next_jump.start_actions)
                self.interpreter.on_jump_start__after_actions(jump=next_jump)

        self.knowledge_base -= self.pointer
        self.knowledge_base += new_pointer

    def do_actions(self, actions):
        for action in actions:
            action.do(self.interpreter)

    def do_step(self):
        if self.can_do_step():
            self.step()
            return True

        if self.is_processed:
            return False

        if self.next_state:
            self.satisfy_requirements(self.next_state)

        return True

    def satisfy_requirements(self, state):
        if self.unsatisfied_requirements:
            self.unsatisfied_requirements[0].satisfy(self.interpreter)

    def can_do_step(self):
        if self.is_processed:
            return False

        if self.pointer.jump is None:
            return True

        return self.next_state is not None and self.check_requirements(self.next_state)

    def check_requirements(self, state):
        self.unsatisfied_requirements = [requirement
                                         for requirement in state.require
                                         if not requirement.check(self.interpreter)]
        return not self.unsatisfied_requirements


    def step_until_can(self):
        while self.can_do_step():
            self.step()

    def sync_pointer(self):
        if self.current_state is None:
            return

        new_pointer = self.pointer.change(jump=self.get_next_jump(self.current_state).uid)

        if new_pointer.jump is not None and self.pointer.jump != new_pointer.jump:
            next_jump = self.knowledge_base[new_pointer.jump]
            self.interpreter.on_jump_start__before_actions(jump=next_jump)
            self.do_actions(next_jump.start_actions)
            self.interpreter.on_jump_start__after_actions(jump=next_jump)

            self.knowledge_base -= self.pointer
            self.knowledge_base += new_pointer


    def get_next_jump(self, state, single=True):
        jumps = self.get_available_jumps(state)
        if not jumps:
            raise exceptions.NoJumpsAvailableError(state=state)
        if single and len(jumps) > 1:
            raise exceptions.MoreThenOneJumpsAvailableError(state=state)
        return random.choice(jumps)

    def get_available_jumps(self, state):
        if isinstance(state, facts.Choice):
            defaults = [default for default in self.knowledge_base.filter(facts.ChoicePath) if default.choice == state.uid]
            return [self.knowledge_base[default.option] for default in defaults]

        if isinstance(state, facts.Question):
            condition = all(requirement.check(self.interpreter) for requirement in state.condition)
            return [answer for answer in self.knowledge_base.filter(facts.Answer) if answer.state_from == state.uid and answer.condition == condition]

        return [jump
                for jump in self.knowledge_base.filter(facts.Jump)
                if jump.state_from == state.uid]

    def get_nearest_choice(self):
        current_state = self.current_state

        if current_state is None:
            current_state = self.get_start_state()

        first_step = True

        while not isinstance(current_state, (facts.Finish, facts.Question)) and (first_step or not isinstance(current_state, facts.Start)):

            first_step = False

            if isinstance(current_state, facts.Choice):
                options = [option for option in self.knowledge_base.filter(facts.Option) if option.state_from == current_state.uid]
                defaults = [default for default in self.knowledge_base.filter(facts.ChoicePath) if default.choice == current_state.uid]
                return (current_state, options, defaults)

            current_state = self.knowledge_base[self.get_next_jump(current_state, single=True).state_to]

        return (None, None, None)
Exemple #4
0
 def pointer(self):
     if self.POINTER_UID not in self.knowledge_base:
         self.knowledge_base += facts.Pointer()
     return self.knowledge_base[self.POINTER_UID]