Browse Source

engulf: cell eating

Bastien Sevajol 7 years ago
parent
commit
559ead41cf

+ 58 - 5
sandbox/engulf/behaviour.py View File

@@ -10,6 +10,10 @@ from synergine2.xyz import ProximitySubjectMechanism, DIRECTIONS, DIRECTION_SLIG
10 10
     get_direction_from_north_degree
11 11
 from synergine2.xyz_utils import get_around_positions_of_positions, get_around_positions_of, get_position_for_direction
12 12
 
13
+# Import for typing hint
14
+if False:
15
+    from sandbox.engulf.subject import Grass
16
+
13 17
 
14 18
 class GrassGrownUp(Event):
15 19
     def __init__(self, subject_id, density, *args, **kwargs):
@@ -127,6 +131,9 @@ class EatableDirectProximityMechanism(ProximitySubjectMechanism):
127 131
     distance = 1.41  # distance when on angle
128 132
     feel_collections = [COLLECTION_GRASS]
129 133
 
134
+    def acceptable_subject(self, subject: 'Grass') -> bool:
135
+        return subject.density >= self.config.simulation.eat_grass_required_density
136
+
130 137
 
131 138
 class MoveTo(Event):
132 139
     def __init__(self, subject_id: int, position: tuple, *args, **kwargs):
@@ -142,6 +149,14 @@ class MoveTo(Event):
142 149
         )
143 150
 
144 151
 
152
+class EatEvent(Event):
153
+    def __init__(self, eaten_id: int, eater_id: int, eaten_new_density: float, *args, **kwargs):
154
+        super().__init__(*args, **kwargs)
155
+        self.eaten_id = eaten_id
156
+        self.eater_id = eater_id
157
+        self.eaten_new_density = eaten_new_density
158
+
159
+
145 160
 class SearchFood(SubjectBehaviour):
146 161
     """
147 162
     Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
@@ -149,6 +164,9 @@ class SearchFood(SubjectBehaviour):
149 164
     use = [EatableDirectProximityMechanism]
150 165
 
151 166
     def run(self, data):
167
+        if self.subject.appetite < self.config.simulation.search_food_appetite_required:
168
+            return False
169
+
152 170
         if not data[EatableDirectProximityMechanism]:
153 171
             return False
154 172
 
@@ -169,7 +187,28 @@ class Eat(SubjectBehaviour):
169 187
     Prduit un immobilisme si sur une case de nourriture, dans le cas ou la cellule n'est as rassasié.
170 188
     """
171 189
     def run(self, data):
172
-        pass
190
+        if self.subject.appetite < self.config.simulation.eat_grass_required_density:
191
+            return False
192
+
193
+        for grass in self.simulation.collections.get(COLLECTION_GRASS, []):
194
+            if grass.position == self.subject.position:
195
+                return grass.id
196
+
197
+    def action(self, data) -> [Event]:
198
+        subject_id = data
199
+
200
+        grass = self.simulation.subjects.index[subject_id]  # TODO: cas ou grass disparu ?
201
+        grass.density -= self.config.simulation.eat_grass_density_reduction
202
+
203
+        self.subject.appetite -= self.config.simulation.eat_grass_appetite_reduction
204
+
205
+        # TODO: Comment mettre des logs (ne peuvent pas être passé aux subprocess)?
206
+
207
+        return [EatEvent(
208
+            eater_id=self.subject.id,
209
+            eaten_id=subject_id,
210
+            eaten_new_density=grass.density,
211
+        )]
173 212
 
174 213
 
175 214
 class Explore(SubjectBehaviour):
@@ -195,12 +234,26 @@ class Explore(SubjectBehaviour):
195 234
         return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
196 235
 
197 236
 
237
+class Hungry(SubjectBehaviour):
238
+    def run(self, data):
239
+        return True
240
+
241
+    def action(self, data) -> [Event]:
242
+        self.subject.appetite += self.config.simulation.hungry_reduction
243
+        return []
244
+
245
+    def get_random_direction(self):
246
+        if not self.subject.previous_direction:
247
+            return choice(DIRECTIONS)
248
+        return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
249
+
250
+
198 251
 class CellBehaviourSelector(SubjectBehaviourSelector):
199 252
     # If behaviour in sublist, only one be kept in sublist
200 253
     behaviour_hierarchy = (  # TODO: refact it
201 254
         (
202
-            Eat,
203
-            SearchFood,
255
+            Eat,  # TODO: Introduce priority with appetite
256
+            SearchFood,  # TODO: Introduce priority with appetite
204 257
             Explore,
205 258
         ),
206 259
     )
@@ -253,9 +306,9 @@ class CellBehaviourSelector(SubjectBehaviourSelector):
253 306
             if behaviour_class != exclude_behaviour_class:
254 307
                 try:
255 308
                     behaviour_position = sublist.index(behaviour_class)
309
+                    if position is None or behaviour_position < position:
310
+                        position = behaviour_position
256 311
                 except ValueError:
257 312
                     pass
258
-                if position is None or behaviour_position < position:
259
-                    position = behaviour_position
260 313
 
261 314
         return position

+ 7 - 0
sandbox/engulf/config.yaml View File

@@ -2,3 +2,10 @@ core:
2 2
     cycle_duration: 0.5
3 3
 terminals:
4 4
     sync: True
5
+simulation:
6
+    start_appetite: 50
7
+    hungry_reduction: 0.25
8
+    search_food_appetite_required: 30
9
+    eat_grass_required_density: 25
10
+    eat_grass_appetite_reduction: 5
11
+    eat_grass_density_reduction: 25

+ 11 - 1
sandbox/engulf/gui.py View File

@@ -4,7 +4,7 @@ from random import randint
4 4
 import cocos
5 5
 from cocos.actions import MoveTo, Repeat, ScaleBy, Reverse, RotateTo
6 6
 from cocos.sprite import Sprite
7
-from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo as MoveToEvent
7
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo as MoveToEvent, EatEvent
8 8
 from sandbox.engulf.subject import Cell, Grass
9 9
 from synergine2.terminals import TerminalPackage
10 10
 from synergine2_cocos2d.gui import Gui, GridLayerMixin
@@ -101,6 +101,10 @@ class Game(Gui):
101 101
             MoveToEvent,
102 102
             self.on_move_to,
103 103
         )
104
+        self.terminal.register_event_handler(
105
+            EatEvent,
106
+            self.on_eat,
107
+        )
104 108
 
105 109
     def get_main_scene(self):
106 110
         return self.main_scene
@@ -135,3 +139,9 @@ class Game(Gui):
135 139
             event.subject_id,
136 140
             event.position,
137 141
         )
142
+
143
+    def on_eat(self, event: EatEvent):
144
+        self.main_layer.grasses.set_density(
145
+            event.eaten_id,
146
+            event.eaten_new_density,
147
+        )

+ 15 - 6
sandbox/engulf/run.py View File

@@ -27,7 +27,7 @@ synergine2_ath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__
27 27
 sys.path.append(synergine2_ath)
28 28
 
29 29
 from random import randint, seed
30
-from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, GrassSpawnBehaviour, MoveTo
30
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, GrassSpawnBehaviour, MoveTo, EatEvent
31 31
 
32 32
 from synergine2.config import Config
33 33
 from synergine2.log import get_default_logger
@@ -52,6 +52,7 @@ class GameTerminal(Terminal):
52 52
         GrassGrownUp,
53 53
         GrassSpawn,
54 54
         MoveTo,
55
+        EatEvent,
55 56
     ]
56 57
 
57 58
     def __init__(self, *args, **kwargs):
@@ -71,6 +72,7 @@ class GameTerminal(Terminal):
71 72
 
72 73
 
73 74
 def fill_with_random_cells(
75
+    config,
74 76
     subjects: EngulfSubjects,
75 77
     count: int,
76 78
     start_position: tuple,
@@ -86,6 +88,7 @@ def fill_with_random_cells(
86 88
         )
87 89
         if position not in subjects.cell_xyz:
88 90
             cell = Cell(
91
+                config,
89 92
                 simulation=subjects.simulation,
90 93
                 position=position,
91 94
             )
@@ -94,6 +97,7 @@ def fill_with_random_cells(
94 97
 
95 98
 
96 99
 def fill_with_random_grass(
100
+    config,
97 101
     subjects: EngulfSubjects,
98 102
     start_count: int,
99 103
     start_position: tuple,
@@ -111,6 +115,7 @@ def fill_with_random_grass(
111 115
 
112 116
         if position not in subjects.grass_xyz:
113 117
             grass = Grass(
118
+                config,
114 119
                 simulation=subjects.simulation,
115 120
                 position=position,
116 121
             )
@@ -121,6 +126,7 @@ def fill_with_random_grass(
121 126
         for around in get_around_positions_of(grass.position, distance=density):
122 127
             if around not in subjects.grass_xyz:
123 128
                 new_grass = Grass(
129
+                    config,
124 130
                     simulation=subjects.simulation,
125 131
                     position=around,
126 132
                 )
@@ -131,15 +137,22 @@ def fill_with_random_grass(
131 137
 
132 138
 def main():
133 139
     seed(0)
134
-    simulation = Engulf()
140
+
141
+    config = Config()
142
+    config.load_files(['sandbox/engulf/config.yaml'])
143
+    logger = get_default_logger(level=logging.DEBUG)
144
+
145
+    simulation = Engulf(config)
135 146
     subjects = EngulfSubjects(simulation=simulation)
136 147
     fill_with_random_cells(
148
+        config,
137 149
         subjects,
138 150
         30,
139 151
         (-34, -34, 0),
140 152
         (34, 34, 0),
141 153
     )
142 154
     fill_with_random_grass(
155
+        config,
143 156
         subjects,
144 157
         5,
145 158
         (-34, -34, 0),
@@ -147,10 +160,6 @@ def main():
147 160
     )
148 161
     simulation.subjects = subjects
149 162
 
150
-    config = Config()
151
-    config.load_files(['sandbox/engulf/config.yaml'])
152
-    logger = get_default_logger(level=logging.DEBUG)
153
-
154 163
     core = Core(
155 164
         config=config,
156 165
         logger=logger,

+ 19 - 1
sandbox/engulf/subject.py View File

@@ -1,5 +1,5 @@
1 1
 # coding: utf-8
2
-from sandbox.engulf.behaviour import GrowUp, SearchFood, Eat, Explore, CellBehaviourSelector
2
+from sandbox.engulf.behaviour import GrowUp, SearchFood, Eat, Explore, CellBehaviourSelector, Hungry
3 3
 from sandbox.engulf.const import COLLECTION_CELL, COLLECTION_ALIVE, COLLECTION_EATABLE, COLLECTION_GRASS
4 4
 from synergine2.simulation import Subject
5 5
 from synergine2.xyz import XYZSubjectMixin
@@ -16,9 +16,27 @@ class Cell(XYZSubjectMixin, Subject):
16 16
         SearchFood,
17 17
         Eat,
18 18
         Explore,
19
+        Hungry,
19 20
     ]
20 21
     behaviour_selector_class = CellBehaviourSelector
21 22
 
23
+    def __init__(self, *args, **kwargs):
24
+        super().__init__(*args, **kwargs)
25
+        self._appetite = self.config.simulation.start_appetite  # /100
26
+
27
+    @property
28
+    def appetite(self) -> float:
29
+        return self._appetite
30
+
31
+    @appetite.setter
32
+    def appetite(self, value) -> None:
33
+        if value > 100:
34
+            self._appetite = 100
35
+        elif value < 0:
36
+            self._appetite = 0
37
+        else:
38
+            self._appetite = value
39
+
22 40
 
23 41
 class Grass(XYZSubjectMixin, Subject):
24 42
     collections = [

+ 25 - 3
synergine2/simulation.py View File

@@ -3,6 +3,7 @@ import collections
3 3
 import typing
4 4
 
5 5
 from synergine2.base import BaseObject
6
+from synergine2.config import Config
6 7
 from synergine2.utils import get_mechanisms_classes
7 8
 
8 9
 
@@ -11,7 +12,12 @@ class Subject(object):
11 12
     behaviours_classes = []
12 13
     behaviour_selector_class = None  # type: typing.Type[SubjectBehaviourSelector]
13 14
 
14
-    def __init__(self, simulation: 'Simulation'):
15
+    def __init__(
16
+        self,
17
+        config: Config,
18
+        simulation: 'Simulation',
19
+    ):
20
+        self.config = config
15 21
         self.id = id(self)  # We store object id because it's lost between process
16 22
         self.simulation = simulation
17 23
         self.behaviours = {}
@@ -37,12 +43,14 @@ class Subject(object):
37 43
     def initialize(self):
38 44
         for mechanism_class in get_mechanisms_classes(self):
39 45
             self.mechanisms[mechanism_class] = mechanism_class(
46
+                config=self.config,
40 47
                 simulation=self.simulation,
41 48
                 subject=self,
42 49
             )
43 50
 
44 51
         for behaviour_class in self.behaviours_classes:
45 52
             self.behaviours[behaviour_class] = behaviour_class(
53
+                config=self.config,
46 54
                 simulation=self.simulation,
47 55
                 subject=self,
48 56
             )
@@ -86,7 +94,11 @@ class Simulation(object):
86 94
     accepted_subject_class = Subjects
87 95
     behaviours_classes = []
88 96
 
89
-    def __init__(self):
97
+    def __init__(
98
+        self,
99
+        config: Config,
100
+    ):
101
+        self.config = config
90 102
         self.collections = collections.defaultdict(list)
91 103
         self._subjects = None
92 104
         self.behaviours = {}
@@ -109,11 +121,13 @@ class Simulation(object):
109 121
     def initialize(self):
110 122
         for mechanism_class in get_mechanisms_classes(self):
111 123
             self.mechanisms[mechanism_class] = mechanism_class(
124
+                config=self.config,
112 125
                 simulation=self,
113 126
             )
114 127
 
115 128
         for behaviour_class in self.behaviours_classes:
116 129
             self.behaviours[behaviour_class] = behaviour_class(
130
+                config=self.config,
117 131
                 simulation=self,
118 132
             )
119 133
 
@@ -121,9 +135,11 @@ class Simulation(object):
121 135
 class SubjectMechanism(BaseObject):
122 136
     def __init__(
123 137
             self,
138
+            config: Config,
124 139
             simulation: Simulation,
125 140
             subject: Subject,
126 141
     ):
142
+        self.config = config
127 143
         self.simulation = simulation
128 144
         self.subject = subject
129 145
 
@@ -137,8 +153,10 @@ class SimulationMechanism(BaseObject):
137 153
 
138 154
     def __init__(
139 155
             self,
156
+            config: Config,
140 157
             simulation: Simulation,
141 158
     ):
159
+        self.config = config
142 160
         self.simulation = simulation
143 161
 
144 162
     def repr_debug(self) -> str:
@@ -162,9 +180,11 @@ class SubjectBehaviour(BaseObject):
162 180
 
163 181
     def __init__(
164 182
             self,
183
+            config: Config,
165 184
             simulation: Simulation,
166 185
             subject: Subject,
167 186
     ):
187
+        self.config = config
168 188
         self.simulation = simulation
169 189
         self.subject = subject
170 190
 
@@ -177,7 +197,7 @@ class SubjectBehaviour(BaseObject):
177 197
         Note: Returned data will be transfered from sub processes.
178 198
               Prefer scalar types.
179 199
         """
180
-        raise NotImplementedError()
200
+        raise NotImplementedError()  # TODO Test it and change to strictly False
181 201
 
182 202
     def action(self, data) -> [Event]:
183 203
         """
@@ -193,8 +213,10 @@ class SimulationBehaviour(BaseObject):
193 213
 
194 214
     def __init__(
195 215
             self,
216
+            config: Config,
196 217
             simulation: Simulation,
197 218
     ):
219
+        self.config = config
198 220
         self.simulation = simulation
199 221
 
200 222
     def run(self, data):

+ 4 - 1
synergine2/xyz.py View File

@@ -152,7 +152,7 @@ class ProximityMixin(object):
152 152
                     ),
153 153
                     self.distance_round_decimals,
154 154
                 )
155
-                if distance <= self.distance:
155
+                if distance <= self.distance and self.acceptable_subject(subject):
156 156
                     direction = round(
157 157
                         get_degree_from_north(
158 158
                             position,
@@ -176,6 +176,9 @@ class ProximityMixin(object):
176 176
             subject.position,
177 177
         )
178 178
 
179
+    def acceptable_subject(self, subject: Subject) -> bool:
180
+        pass
181
+
179 182
 
180 183
 class ProximitySubjectMechanism(ProximityMixin, SubjectMechanism):
181 184
     def run(self):