Преглед изворни кода

Merge branch 'feature/composed_behaviour'

Bastien Sevajol пре 6 година
родитељ
комит
2b129ee52b

+ 59 - 1
synergine2/simulation.py Прегледај датотеку

@@ -6,6 +6,7 @@ import time
6 6
 from synergine2.base import BaseObject
7 7
 from synergine2.base import IdentifiedObject
8 8
 from synergine2.config import Config
9
+from synergine2.exceptions import ConfigurationError
9 10
 from synergine2.share import shared
10 11
 from synergine2.utils import get_mechanisms_classes
11 12
 
@@ -33,7 +34,7 @@ class IntentionManager(object):
33 34
 
34 35
     def remove(self, intention_type: typing.Type[Intention]) -> None:
35 36
         intentions = self.intentions
36
-        del self.intentions[intention_type]
37
+        del intentions[intention_type]
37 38
         self.intentions = intentions
38 39
 
39 40
     def remove_all(self) -> None:
@@ -412,3 +413,60 @@ class SubjectBehaviourSelector(BaseObject):
412 413
         behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
413 414
     ) -> typing.Dict[typing.Type[SubjectBehaviour], object]:
414 415
         return behaviours
416
+
417
+
418
+class BehaviourStep(object):
419
+    def proceed(self) -> 'BehaviourStep':
420
+        raise NotImplementedError()
421
+
422
+    def generate_data(self) -> typing.Any:
423
+        raise NotImplementedError()
424
+
425
+    def get_events(self) -> typing.List[Event]:
426
+        raise NotImplementedError()
427
+
428
+
429
+class SubjectComposedBehaviour(SubjectBehaviour):
430
+    """
431
+    SubjectComposedBehaviour receive data in run (and will will it's step with it).
432
+    These data can be the first data of behaviour, or last behaviour subject data
433
+    produced in action.
434
+    SubjectComposedBehaviour produce data in run only if something happen and must be
435
+    given in future run.
436
+    """
437
+    step_classes = None  # type: typing.List[typing.Tuple[typing.Type[BehaviourStep], typing.Callable[[typing.Any], bool]]]  # nopep8
438
+
439
+    def __init__(
440
+        self,
441
+        config: Config,
442
+        simulation: Simulation,
443
+        subject: Subject,
444
+    ) -> None:
445
+        super().__init__(config, simulation, subject)
446
+        if self.step_classes is None:
447
+            raise ConfigurationError(
448
+                '{}: you must set step_classes class attribute'.format(
449
+                    self.__class__.__name__,
450
+                ),
451
+            )
452
+
453
+    def get_step(self, data) -> BehaviourStep:
454
+        for step_class, step_test_callable in self.step_classes:
455
+            if step_test_callable(data):
456
+                return step_class(**data)
457
+
458
+        raise ConfigurationError(
459
+            '{}: No step choose for following data: {}'.format(
460
+                self.__class__.__name__,
461
+                data,
462
+            ),
463
+        )
464
+
465
+    def run(self, data):
466
+        step = self.get_step(data)
467
+        next_step = step.proceed()
468
+        return next_step.generate_data()
469
+
470
+    def action(self, data):
471
+        step = self.get_step(data)
472
+        return step.get_events()

+ 11 - 9
synergine2_xyz/move/intention.py Прегледај датотеку

@@ -7,14 +7,16 @@ from synergine2.simulation import Intention
7 7
 class MoveToIntention(Intention):
8 8
     def __init__(
9 9
         self,
10
-        move_to: typing.Tuple[int, int],
11
-        start_time: float,
10
+        to: typing.Tuple[int, int],
12 11
         gui_action: typing.Any,
13 12
     ) -> None:
14
-        self.move_to = move_to
15
-        self.path = []  # type: typing.List[typing.Tuple[int, int]]
16
-        self.path_progression = -1  # type: int
17
-        self.last_intention_time = start_time
18
-        self.just_reach = False
19
-        self.initial = True
20
-        self.gui_action = gui_action
13
+        self.to = to
14
+        self.gui_action = gui_action
15
+        self.path = None  # type: typing.List[typing.Tuple[int, int]]
16
+
17
+    def get_data(self) -> dict:
18
+        return {
19
+            'to': self.to,
20
+            'gui_action': self.gui_action,
21
+            'path': self.path,
22
+        }

+ 0 - 233
synergine2_xyz/move/simulation.py Прегледај датотеку

@@ -1,233 +0,0 @@
1
-# coding: utf-8
2
-import time
3
-import typing
4
-
5
-from synergine2.config import Config
6
-from synergine2.simulation import SimulationBehaviour
7
-from synergine2.simulation import Simulation
8
-from synergine2.simulation import Event
9
-from synergine2.simulation import SubjectMechanism
10
-from synergine2.simulation import SubjectBehaviour
11
-from synergine2_xyz.move.intention import MoveToIntention
12
-from synergine2_xyz.simulation import XYZSimulation
13
-
14
-
15
-class RequestMoveBehaviour(SimulationBehaviour):
16
-    move_intention_class = MoveToIntention
17
-
18
-    @classmethod
19
-    def merge_data(cls, new_data, start_data=None):
20
-        # TODO: behaviour/Thing dedicated to Gui -> Simulation ?
21
-        pass  # This behaviour is designed to be launch by terminal
22
-
23
-    def __init__(
24
-        self,
25
-        config: Config,
26
-        simulation: Simulation,
27
-    ):
28
-        super().__init__(config, simulation)
29
-        self.simulation = typing.cast(XYZSimulation, self.simulation)
30
-
31
-    def run(self, data):
32
-        # TODO: behaviour/Thing dedicated to Gui -> Simulation ?
33
-        pass  # This behaviour is designed to be launch by terminal
34
-
35
-    def action(self, data) -> typing.List[Event]:
36
-        subject_id = data['subject_id']
37
-        move_to = data['move_to']
38
-
39
-        try:
40
-            subject = self.simulation.subjects.index[subject_id]
41
-            subject.intentions.set(self.move_intention_class(
42
-                move_to,
43
-                start_time=time.time(),
44
-                gui_action=data['gui_action'],
45
-            ))
46
-        except KeyError:
47
-            # TODO: log error here
48
-            pass
49
-
50
-        return []
51
-
52
-
53
-class MoveToMechanism(SubjectMechanism):
54
-    def run(self):
55
-        # TODO: Si move to: Si nouveau: a*, si bloque, a*, sinon rien
56
-        # TODO: pourquoi un mechanism plutot que dans run du behaviour ? faire en sorte que lorsque on calcule,
57
-        # si un subject est déjà passé par là et qu'il va au même endroit, ne pas recalculer.
58
-        try:
59
-            # TODO: MoveToIntention doit être configurable
60
-            move = self.subject.intentions.get(MoveToIntention)
61
-            move = typing.cast(MoveToIntention, move)
62
-            new_path = None
63
-
64
-            if not move.path:
65
-                move.path = self.simulation.physics.found_path(
66
-                    start=self.subject.position,
67
-                    end=move.move_to,
68
-                    subject=self.subject,
69
-                )
70
-
71
-                # Note: We are in process, move change will be lost
72
-                new_path = move.path
73
-
74
-                # move.path = []
75
-                # new_path = move.path
76
-                # for i in range(20):
77
-                #     move.path.append((
78
-                #         self.subject.position[0],
79
-                #         self.subject.position[1] + i,
80
-                #     ))
81
-
82
-            next_move = move.path[move.path_progression + 1]
83
-            # TODO: fin de path
84
-            if not self.simulation.is_possible_position(next_move):
85
-                # TODO: refaire le path
86
-                new_path = ['...']
87
-
88
-            return {
89
-                'new_path': new_path,
90
-                'last_intention_time': move.last_intention_time,
91
-                'just_reach': move.just_reach,
92
-                'initial': move.initial,
93
-                'gui_action': move.gui_action,
94
-            }
95
-
96
-        except IndexError:  # TODO: Specialize ? No movement left
97
-            return {
98
-                'finished': True,
99
-            }
100
-        except KeyError:  # TODO: Specialize ? No MoveIntention
101
-            return None
102
-
103
-
104
-class FinishMoveEvent(Event):
105
-    def __init__(
106
-        self,
107
-        subject_id: int,
108
-        from_position: typing.Tuple[int, int],
109
-        to_position: typing.Tuple[int, int],
110
-        gui_action: typing.Any,
111
-        move_duration: float=0.0,
112
-        *args,
113
-        **kwargs
114
-    ):
115
-        super().__init__(*args, **kwargs)
116
-        self.subject_id = subject_id
117
-        self.from_position = from_position
118
-        self.to_position = to_position
119
-        self.gui_action = gui_action
120
-        self.move_duration = move_duration
121
-
122
-    def repr_debug(self) -> str:
123
-        return '{}: subject_id:{}, from_position:{} to_position: {}'.format(
124
-            type(self).__name__,
125
-            self.subject_id,
126
-            self.from_position,
127
-            self.to_position,
128
-        )
129
-
130
-
131
-class StartMoveEvent(Event):
132
-    def __init__(
133
-        self,
134
-        subject_id: int,
135
-        from_position: typing.Tuple[int, int],
136
-        to_position: typing.Tuple[int, int],
137
-        gui_action: typing.Any,
138
-        move_duration: float=0.0,
139
-        *args,
140
-        **kwargs
141
-    ):
142
-        super().__init__(*args, **kwargs)
143
-        self.subject_id = subject_id
144
-        self.from_position = from_position
145
-        self.to_position = to_position
146
-        self.gui_action = gui_action
147
-        self.move_duration = move_duration
148
-
149
-    def repr_debug(self) -> str:
150
-        return '{}: subject_id:{}, from_position:{} to_position: {}'.format(
151
-            type(self).__name__,
152
-            self.subject_id,
153
-            self.from_position,
154
-            self.to_position,
155
-        )
156
-
157
-
158
-class MoveToBehaviour(SubjectBehaviour):
159
-    move_to_mechanism = MoveToMechanism
160
-    use = [move_to_mechanism]
161
-
162
-    def run(self, data):
163
-        move_to_data = data[self.move_to_mechanism]
164
-        if move_to_data:
165
-            if move_to_data.get('finished'):
166
-                return move_to_data
167
-
168
-            if self._can_move_to_next_step(move_to_data):
169
-                move_to_data['reach_next'] = True
170
-                return move_to_data
171
-
172
-            if self._is_fresh_new_step(move_to_data):
173
-                move_to_data['reach_next'] = False
174
-                return move_to_data
175
-
176
-        return False
177
-
178
-    def _can_move_to_next_step(self, move_to_data: dict) -> bool:
179
-        raise NotImplementedError()
180
-
181
-    def _is_fresh_new_step(self, move_to_data: dict) -> bool:
182
-        return move_to_data['just_reach'] or move_to_data['initial']
183
-
184
-    def finalize_event(self, move_to_data: dict, event: Event) -> None:
185
-        pass
186
-
187
-    def action(self, data) -> [Event]:
188
-        # TODO: MoveToIntention doit être configurable
189
-        try:
190
-            if data.get('finished'):
191
-                self.subject.intentions.remove(MoveToIntention)
192
-                return []
193
-            move = self.subject.intentions.get(MoveToIntention)
194
-        except KeyError:  # TODO: Specialize exception
195
-            # Action don't exist anymore
196
-            return []
197
-
198
-        move = typing.cast(MoveToIntention, move)
199
-        new_path = data['new_path']
200
-        if new_path:
201
-            move.path = new_path
202
-            move.path_progression = -1
203
-
204
-        previous_position = self.subject.position
205
-        new_position = move.path[move.path_progression + 1]
206
-
207
-        if data['reach_next']:
208
-            # TODO: fin de path
209
-            move.path_progression += 1
210
-            self.subject.position = new_position
211
-            move.last_intention_time = time.time()
212
-            move.just_reach = True
213
-            event = FinishMoveEvent(
214
-                self.subject.id,
215
-                previous_position,
216
-                new_position,
217
-                gui_action=move.gui_action,
218
-            )
219
-        else:
220
-            move.just_reach = False
221
-            event = StartMoveEvent(
222
-                self.subject.id,
223
-                previous_position,
224
-                new_position,
225
-                gui_action=move.gui_action,
226
-            )
227
-
228
-        move.initial = False
229
-        # Note: Need to explicitly set to update shared data
230
-        self.subject.intentions.set(move)
231
-        self.finalize_event(data, event)
232
-
233
-        return [event]

+ 165 - 0
tests/test_composed_behaviour.py Прегледај датотеку

@@ -0,0 +1,165 @@
1
+# coding: utf-8
2
+import typing
3
+
4
+from synergine2.config import Config
5
+from synergine2.simulation import SubjectComposedBehaviour
6
+from synergine2.simulation import BehaviourStep
7
+from synergine2.simulation import Event
8
+from synergine2_xyz.simulation import XYZSimulation
9
+from synergine2_xyz.subjects import XYZSubject
10
+from tests import BaseTest
11
+
12
+
13
+class BeginFirstEvent(Event):
14
+    pass
15
+
16
+
17
+class FinishFirstEvent(Event):
18
+    pass
19
+
20
+
21
+class BeginSecondEvent(Event):
22
+    pass
23
+
24
+
25
+class FinishSecondEvent(Event):
26
+    pass
27
+
28
+
29
+class BeginBaseEvent(Event):
30
+    pass
31
+
32
+
33
+class FinishBaseEvent(Event):
34
+    pass
35
+
36
+
37
+class MyFirstStep(BehaviourStep):
38
+    def __init__(self, base: int, first: float=-0.5):
39
+        self.base = base
40
+        self.first = first
41
+
42
+    def proceed(self) -> 'BehaviourStep':
43
+        self.first += 0.5
44
+
45
+        if self.first == 1:
46
+            next_step = MySecondStep(
47
+                base=self.base,
48
+            )
49
+            next_step.proceed()
50
+            return next_step
51
+
52
+        return self
53
+
54
+    def generate_data(self) -> typing.Any:
55
+        return {
56
+            'base': self.base,
57
+            'first': self.first,
58
+        }
59
+
60
+    def get_events(self) -> typing.List[Event]:
61
+        if self.base == 0 and self.first == 0:
62
+            return [BeginBaseEvent(), BeginFirstEvent()]
63
+
64
+        if self.first == 0:
65
+            return [BeginFirstEvent()]
66
+
67
+        if self.first == 1:
68
+            return [FinishFirstEvent(), BeginSecondEvent()]
69
+
70
+        return []
71
+
72
+
73
+class MySecondStep(BehaviourStep):
74
+    def __init__(self, base: int, second: float=-0.5):
75
+        self.base = base
76
+        self.second = second
77
+
78
+    def proceed(self) -> 'BehaviourStep':
79
+        self.second += 0.5
80
+
81
+        if self.second == 1:
82
+            self.base = 1
83
+
84
+        return self
85
+
86
+    def get_events(self) -> typing.List[Event]:
87
+        if self.second == 0:
88
+            return [BeginSecondEvent(), FinishFirstEvent()]
89
+
90
+        if self.second == 1:
91
+            return [FinishSecondEvent(), FinishBaseEvent()]
92
+
93
+        return []
94
+
95
+    def generate_data(self) -> typing.Any:
96
+        return {
97
+            'base': self.base,
98
+            'second': self.second,
99
+        }
100
+
101
+
102
+class MyComposedBehaviour(SubjectComposedBehaviour):
103
+    step_classes = [
104
+        (MySecondStep, lambda d: 'second' in d),
105
+        (MyFirstStep, lambda d: 'first' in d or d.get('base') == 0),
106
+    ]
107
+
108
+
109
+class TestComposedBehaviour(BaseTest):
110
+    def test_subject_composed_behaviour(self):
111
+        config = Config({})
112
+        simulation = XYZSimulation(config)
113
+        subject = XYZSubject(config, simulation)
114
+
115
+        my_composed_behaviour = MyComposedBehaviour(
116
+            config=config,
117
+            simulation=simulation,
118
+            subject=subject,
119
+        )
120
+
121
+        # Thirst cycle, ThirstStep is reached and produce event
122
+        data = my_composed_behaviour.run({
123
+            'base': 0,
124
+        })
125
+        assert {'base': 0, 'first': 0} == data
126
+
127
+        events = my_composed_behaviour.action(data)
128
+        assert events
129
+        assert 2 == len(events)
130
+        assert isinstance(events[0], BeginBaseEvent)
131
+        assert isinstance(events[1], BeginFirstEvent)
132
+
133
+        # Second cycle (with previous cycle data), there is data but no events
134
+        data = my_composed_behaviour.run(data)
135
+        assert {'base': 0, 'first': 0.5} == data
136
+
137
+        events = my_composed_behaviour.action(data)
138
+        assert not events
139
+
140
+        # Third cycle (still with previous cycle data) there is data but and events
141
+        data = my_composed_behaviour.run(data)
142
+        assert {'base': 0, 'second': 0} == data
143
+
144
+        events = my_composed_behaviour.action(data)
145
+        assert events
146
+        assert 2 == len(events)
147
+        assert isinstance(events[0], BeginSecondEvent)
148
+        assert isinstance(events[1], FinishFirstEvent)
149
+
150
+        # Fourth cycle (still with previous cycle data) there is data but no events
151
+        data = my_composed_behaviour.run(data)
152
+        assert {'base': 0, 'second': 0.5} == data
153
+
154
+        events = my_composed_behaviour.action(data)
155
+        assert not events
156
+
157
+        # Cycle 5 (still with previous cycle data) there is data and events
158
+        data = my_composed_behaviour.run(data)
159
+        assert {'base': 1, 'second': 1} == data
160
+
161
+        events = my_composed_behaviour.action(data)
162
+        assert events
163
+        assert 2 == len(events)
164
+        assert isinstance(events[0], FinishSecondEvent)
165
+        assert isinstance(events[1], FinishBaseEvent)

+ 0 - 166
tests/test_move.py Прегледај датотеку

@@ -1,166 +0,0 @@
1
-# coding: utf-8
2
-import time
3
-
4
-from freezegun import freeze_time
5
-
6
-from synergine2.config import Config
7
-from synergine2.simulation import Simulation, Subject
8
-from synergine2_cocos2d.user_action import UserAction as BaseUserAction
9
-from synergine2_xyz.move.intention import MoveToIntention
10
-from synergine2_xyz.move.simulation import MoveToMechanism
11
-from synergine2_xyz.move.simulation import StartMoveEvent
12
-from synergine2_xyz.move.simulation import FinishMoveEvent
13
-from synergine2_xyz.simulation import XYZSimulation
14
-from synergine2_xyz.subjects import XYZSubject
15
-from tests import BaseTest
16
-from synergine2_xyz.move.simulation import MoveToBehaviour as BaseMoveToBehaviour
17
-
18
-
19
-class MyMoveToBehaviour(BaseMoveToBehaviour):
20
-    def __init__(
21
-        self,
22
-        config: Config,
23
-        simulation: Simulation,
24
-        subject: Subject,
25
-    ) -> None:
26
-        super().__init__(config, simulation, subject)
27
-        self._walk_duration = float(self.config.resolve('game.move.walk_ref_time'))
28
-
29
-    def _can_move_to_next_step(self, move_to_data: dict) -> bool:
30
-        if move_to_data['gui_action'] == UserAction.ORDER_MOVE:
31
-            return time.time() - move_to_data['last_intention_time'] >= self._walk_duration
32
-
33
-
34
-class UserAction(BaseUserAction):
35
-    ORDER_MOVE = 'ORDER_MOVE'
36
-
37
-
38
-class MySubject(XYZSubject):
39
-    pass
40
-
41
-
42
-class MySimulation(XYZSimulation):
43
-    pass
44
-
45
-
46
-class TestMove(BaseTest):
47
-    def test_behaviour_cycle(self):
48
-        config = Config({
49
-            'game': {
50
-                'move': {
51
-                    'walk_ref_time': 2,
52
-                }
53
-            }
54
-        })
55
-        simulation = MySimulation(config)
56
-        subject = MySubject(config, simulation)
57
-        behaviour = MyMoveToBehaviour(config, simulation, subject)
58
-
59
-        with freeze_time("2000-01-01 00:00:00"):
60
-            move_intention = MoveToIntention((0, 3), time.time(), gui_action=UserAction.ORDER_MOVE)
61
-
62
-            assert move_intention.path_progression == -1
63
-            assert move_intention.just_reach is False
64
-            assert move_intention.initial is True
65
-
66
-            subject.intentions.set(move_intention)
67
-            move_data = {
68
-                'new_path': [(0, 1), (0, 2)],
69
-                'last_intention_time': time.time(),
70
-                'just_reach': False,
71
-                'initial': True,
72
-                'gui_action': UserAction.ORDER_MOVE,
73
-            }
74
-            data = {
75
-                MoveToMechanism: move_data,
76
-            }
77
-            run_data = behaviour.run(data)
78
-
79
-            assert move_data == run_data
80
-            events = behaviour.action(run_data)
81
-
82
-        assert events
83
-        assert 1 == len(events)
84
-        assert isinstance(events[0], StartMoveEvent)
85
-
86
-        assert move_intention.path_progression == -1
87
-        assert move_intention.just_reach is False
88
-        assert move_intention.initial is False
89
-
90
-        # Update data like mechanism do it
91
-        move_data['last_intention_time'] = move_intention.last_intention_time
92
-        move_data['just_reach'] = move_intention.just_reach
93
-        move_data['initial'] = move_intention.initial
94
-
95
-        # Only one second, no reach
96
-        with freeze_time("2000-01-01 00:00:01"):
97
-            run_data = behaviour.run(data)
98
-
99
-        assert run_data is False
100
-
101
-        # Two second, step reach
102
-        with freeze_time("2000-01-01 00:00:02"):
103
-            run_data = behaviour.run(data)
104
-
105
-            assert {
106
-                       'new_path': [(0, 1), (0, 2)],
107
-                       'initial': False,
108
-                       'just_reach': False,
109
-                       'last_intention_time': 946684800.0,
110
-                       'reach_next': True,
111
-                       'gui_action': UserAction.ORDER_MOVE,
112
-                   } == run_data
113
-
114
-            events = behaviour.action(run_data)
115
-
116
-            assert events
117
-            assert 1 == len(events)
118
-            assert isinstance(events[0], FinishMoveEvent)
119
-
120
-        # Update data like mechanism do it
121
-        move_data['last_intention_time'] = move_intention.last_intention_time
122
-        move_data['just_reach'] = move_intention.just_reach
123
-        move_data['initial'] = move_intention.initial
124
-
125
-        # Three seconds, start a new move
126
-        with freeze_time("2000-01-01 00:00:03"):
127
-            run_data = behaviour.run(data)
128
-
129
-            assert {
130
-                       'new_path': [(0, 1), (0, 2)],
131
-                       'initial': False,
132
-                       'just_reach': True,
133
-                       'last_intention_time': 946684802.0,
134
-                       'reach_next': False,
135
-                       'gui_action': UserAction.ORDER_MOVE,
136
-                   } == run_data
137
-
138
-            events = behaviour.action(run_data)
139
-
140
-            assert events
141
-            assert 1 == len(events)
142
-            assert isinstance(events[0], StartMoveEvent)
143
-
144
-        # Update data like mechanism do it
145
-        move_data['last_intention_time'] = move_intention.last_intention_time
146
-        move_data['just_reach'] = move_intention.just_reach
147
-        move_data['initial'] = move_intention.initial
148
-
149
-        # Four seconds, start a new move
150
-        with freeze_time("2000-01-01 00:00:04"):
151
-            run_data = behaviour.run(data)
152
-
153
-            assert {
154
-                       'new_path': [(0, 1), (0, 2)],
155
-                       'initial': False,
156
-                       'just_reach': False,
157
-                       'last_intention_time': 946684802.0,
158
-                       'reach_next': True,
159
-                       'gui_action': UserAction.ORDER_MOVE,
160
-                   } == run_data
161
-
162
-            events = behaviour.action(run_data)
163
-
164
-            assert events
165
-            assert 1 == len(events)
166
-            assert isinstance(events[0], FinishMoveEvent)