Browse Source

engulf: predator eat prey

Bastien Sevajol 8 years ago
parent
commit
7401fa64cd

+ 91 - 15
sandbox/engulf/behaviour.py View File

2
 import typing
2
 import typing
3
 from random import choice
3
 from random import choice
4
 
4
 
5
-from sandbox.engulf.const import COLLECTION_GRASS, COLLECTION_CELL
5
+from sandbox.engulf.const import COLLECTION_GRASS, COLLECTION_CELL, COLLECTION_ALIVE, COLLECTION_PREY
6
 from sandbox.engulf.exceptions import NotFoundWhereToGo
6
 from sandbox.engulf.exceptions import NotFoundWhereToGo
7
 from synergine2.simulation import SubjectBehaviour, SimulationMechanism, SimulationBehaviour, SubjectBehaviourSelector
7
 from synergine2.simulation import SubjectBehaviour, SimulationMechanism, SimulationBehaviour, SubjectBehaviourSelector
8
 from synergine2.simulation import Event
8
 from synergine2.simulation import Event
169
         self.eaten_new_density = eaten_new_density
169
         self.eaten_new_density = eaten_new_density
170
 
170
 
171
 
171
 
172
+class EatenEvent(Event):
173
+    def __init__(self, eaten_id: int, *args, **kwargs):
174
+        super().__init__(*args, **kwargs)
175
+        self.eaten_id = eaten_id
176
+
177
+
172
 class AttackEvent(Event):
178
 class AttackEvent(Event):
173
     def __init__(self, attacker_id: int, attacked_id: int, *args, **kwargs):
179
     def __init__(self, attacker_id: int, attacked_id: int, *args, **kwargs):
174
         super().__init__(*args, **kwargs)
180
         super().__init__(*args, **kwargs)
176
         self.attacked_id = attacked_id
182
         self.attacked_id = attacked_id
177
 
183
 
178
 
184
 
179
-class SearchGrass(SubjectBehaviour):
180
-    """
181
-    Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
182
-    """
183
-    use = [GrassEatableDirectProximityMechanism]
185
+class SearchFood(SubjectBehaviour):
186
+    mechanism_data_class = NotImplemented
187
+
188
+    def get_required_appetite(self) -> float:
189
+        raise NotImplementedError()
184
 
190
 
185
     def run(self, data):
191
     def run(self, data):
186
-        if self.subject.appetite < self.config.simulation.search_food_appetite_required:
192
+        if self.subject.appetite < self.get_required_appetite():
187
             return False
193
             return False
188
 
194
 
189
-        if not data[GrassEatableDirectProximityMechanism]:
195
+        if not data[self.mechanism_data_class]:
190
             return False
196
             return False
191
 
197
 
192
-        direction_degrees = [d['direction'] for d in data[GrassEatableDirectProximityMechanism]]
198
+        direction_degrees = [d['direction'] for d in data[self.mechanism_data_class]]
193
         return get_direction_from_north_degree(choice(direction_degrees))
199
         return get_direction_from_north_degree(choice(direction_degrees))
194
 
200
 
195
     def action(self, data) -> [Event]:
201
     def action(self, data) -> [Event]:
201
         return [MoveTo(self.subject.id, position)]
207
         return [MoveTo(self.subject.id, position)]
202
 
208
 
203
 
209
 
204
-class EatGrass(SubjectBehaviour):
210
+class SearchGrass(SearchFood):
211
+    """
212
+    Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
205
     """
213
     """
206
-    Prduit un immobilisme si sur une case de nourriture, dans le cas ou la cellule n'est as rassasié.
214
+    use = [GrassEatableDirectProximityMechanism]
215
+    mechanism_data_class = use[0]
216
+
217
+    def get_required_appetite(self) -> float:
218
+        return self.config.simulation.search_food_appetite_required
219
+
220
+
221
+class SearchPrey(SearchFood):
222
+    """
223
+    Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
207
     """
224
     """
225
+    use = [PreyEatableDirectProximityMechanism]
226
+    mechanism_data_class = use[0]
227
+
228
+    def get_required_appetite(self) -> float:
229
+        return self.config.simulation.search_and_attack_prey_apetite_required
230
+
231
+
232
+class EatGrass(SubjectBehaviour):
208
     def run(self, data):
233
     def run(self, data):
209
         if self.subject.appetite < self.config.simulation.eat_grass_required_density:
234
         if self.subject.appetite < self.config.simulation.eat_grass_required_density:
210
             return False
235
             return False
231
         )]
256
         )]
232
 
257
 
233
 
258
 
259
+class EatPrey(SubjectBehaviour):
260
+    def run(self, data):
261
+        if self.subject.appetite < self.config.simulation.search_and_attack_prey_apetite_required:
262
+            return False
263
+
264
+        for prey in self.simulation.collections.get(COLLECTION_PREY, []):
265
+            # TODO: Use simulation/xyz pre calculated indexes
266
+            if prey.position == self.subject.position:
267
+                return prey.id
268
+
269
+    def action(self, data) -> [Event]:
270
+        subject_id = data
271
+
272
+        # Cell already eaten ?
273
+        if subject_id not in self.simulation.subjects.index:
274
+            return []
275
+
276
+        prey = self.simulation.subjects.index[subject_id]
277
+        self.simulation.subjects.remove(prey)
278
+        self.subject.appetite -= self.config.simulation.eat_prey_required_density
279
+
280
+        return [EatenEvent(
281
+            eaten_id=prey.id,
282
+        )]
283
+
284
+
234
 class Explore(SubjectBehaviour):
285
 class Explore(SubjectBehaviour):
235
     """
286
     """
236
     Produit un mouvement au hasard (ou un immobilisme)
287
     Produit un mouvement au hasard (ou un immobilisme)
238
     use = []
289
     use = []
239
 
290
 
240
     def run(self, data):
291
     def run(self, data):
241
-        return True  # for now, want move every time
292
+        # TODO: Il faut pouvoir dire tel behaviour concerne que tel collections
293
+        if COLLECTION_ALIVE not in self.subject.collections:
294
+            return False
295
+
296
+        return True
242
 
297
 
243
     def action(self, data) -> [Event]:
298
     def action(self, data) -> [Event]:
244
         try:
299
         try:
290
     use = [PreyEatableDirectProximityMechanism]
345
     use = [PreyEatableDirectProximityMechanism]
291
 
346
 
292
     def run(self, data):
347
     def run(self, data):
293
-        if data[PreyEatableDirectProximityMechanism]:
294
-            return choice(data[PreyEatableDirectProximityMechanism])
348
+        if self.subject.appetite < self.config.simulation.search_and_attack_prey_apetite_required:
349
+            return False
350
+
351
+        eatable_datas = data[PreyEatableDirectProximityMechanism]
352
+        attackable_datas = []
353
+
354
+        for eatable_data in eatable_datas:
355
+            if COLLECTION_ALIVE in eatable_data['subject'].collections:
356
+                attackable_datas.append(eatable_data)
357
+
358
+        if attackable_datas:
359
+            return choice(attackable_datas)
295
         return False
360
         return False
296
 
361
 
297
     def action(self, data) -> [Event]:
362
     def action(self, data) -> [Event]:
298
-        # TODO: Dommages / mort
363
+        attacked = self.simulation.subjects.index[data['subject'].id]
364
+
365
+        try:
366
+            # TODO il faut automatiser/Refactoriser le fait de retirer/ajouter un collection
367
+            self.simulation.collections[COLLECTION_ALIVE].remove(attacked)
368
+            attacked.collections.remove(COLLECTION_ALIVE)
369
+        except ValueError:
370
+            pass  # On considere qu'il a ete tué par une autre, TODO: en être sur ?
371
+
299
         return [AttackEvent(attacker_id=self.subject.id, attacked_id=data['subject'].id)]
372
         return [AttackEvent(attacker_id=self.subject.id, attacked_id=data['subject'].id)]
300
 
373
 
301
 
374
 
303
     # If behaviour in sublist, only one be kept in sublist
376
     # If behaviour in sublist, only one be kept in sublist
304
     behaviour_hierarchy = (  # TODO: refact it
377
     behaviour_hierarchy = (  # TODO: refact it
305
         (
378
         (
379
+            EatPrey,
306
             EatGrass,  # TODO: Introduce priority with appetite
380
             EatGrass,  # TODO: Introduce priority with appetite
381
+            Attack,
382
+            SearchPrey,
307
             SearchGrass,  # TODO: Introduce priority with appetite
383
             SearchGrass,  # TODO: Introduce priority with appetite
308
             Explore,
384
             Explore,
309
         ),
385
         ),

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

6
     start_appetite: 50
6
     start_appetite: 50
7
     hungry_reduction: 0.25
7
     hungry_reduction: 0.25
8
     search_food_appetite_required: 30
8
     search_food_appetite_required: 30
9
+    search_and_attack_prey_apetite_required: 50
9
     eat_grass_required_density: 25
10
     eat_grass_required_density: 25
11
+    eat_prey_required_density: 100
10
     eat_grass_appetite_reduction: 5
12
     eat_grass_appetite_reduction: 5
11
     eat_grass_density_reduction: 25
13
     eat_grass_density_reduction: 25

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

4
 import cocos
4
 import cocos
5
 from cocos.actions import MoveTo, Repeat, ScaleBy, Reverse, RotateTo
5
 from cocos.actions import MoveTo, Repeat, ScaleBy, Reverse, RotateTo
6
 from cocos.sprite import Sprite
6
 from cocos.sprite import Sprite
7
-from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo as MoveToEvent, EatEvent, AttackEvent
7
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo as MoveToEvent, EatEvent, AttackEvent, EatenEvent
8
 from sandbox.engulf.subject import Cell, Grass, PreyCell, PredatorCell
8
 from sandbox.engulf.subject import Cell, Grass, PreyCell, PredatorCell
9
 from synergine2.terminals import TerminalPackage
9
 from synergine2.terminals import TerminalPackage
10
 from synergine2_cocos2d.gui import Gui, GridLayerMixin
10
 from synergine2_cocos2d.gui import Gui, GridLayerMixin
41
         self.add(cell)
41
         self.add(cell)
42
 
42
 
43
     def move(self, subject_id: int, position: tuple):
43
     def move(self, subject_id: int, position: tuple):
44
+        if subject_id not in self.cell_ids:
45
+            return  # Cell eaten before
46
+
44
         cell = self.cell_ids[subject_id]
47
         cell = self.cell_ids[subject_id]
45
 
48
 
46
         window_position = self.grid_manager.get_window_position(position[0], position[1])
49
         window_position = self.grid_manager.get_window_position(position[0], position[1])
55
         attacker = self.cell_ids[attacker_id]
58
         attacker = self.cell_ids[attacker_id]
56
         attacker.do(ScaleBy(1.7, duration=0.25))
59
         attacker.do(ScaleBy(1.7, duration=0.25))
57
 
60
 
61
+    def eaten(self, eaten_id: int):
62
+        eaten = self.cell_ids[eaten_id]
63
+        self.remove(eaten)
64
+        # TODO: refact: On a pas nettoyer self.cell_positions par exemple
65
+        del self.cell_ids[eaten_id]
66
+
58
 
67
 
59
 class GrassLayer(GridLayerMixin, BaseMainLayer):
68
 class GrassLayer(GridLayerMixin, BaseMainLayer):
60
     def __init__(self, game: 'Game', *args, **kwargs):
69
     def __init__(self, game: 'Game', *args, **kwargs):
115
             AttackEvent,
124
             AttackEvent,
116
             self.on_attack,
125
             self.on_attack,
117
         )
126
         )
127
+        self.terminal.register_event_handler(
128
+            EatenEvent,
129
+            self.on_eaten,
130
+        )
118
 
131
 
119
     def get_main_scene(self):
132
     def get_main_scene(self):
120
         return self.main_scene
133
         return self.main_scene
163
             event.attacker_id,
176
             event.attacker_id,
164
             event.attacked_id,
177
             event.attacked_id,
165
         )
178
         )
179
+
180
+    def on_eaten(self, event: EatenEvent):
181
+        self.main_layer.cells.eaten(
182
+            event.eaten_id,
183
+        )

+ 2 - 1
sandbox/engulf/run.py View File

27
 sys.path.append(synergine2_ath)
27
 sys.path.append(synergine2_ath)
28
 
28
 
29
 from random import randint, seed
29
 from random import randint, seed
30
-from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo, EatEvent, AttackEvent
30
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo, EatEvent, AttackEvent, EatenEvent
31
 
31
 
32
 from synergine2.config import Config
32
 from synergine2.config import Config
33
 from synergine2.log import get_default_logger
33
 from synergine2.log import get_default_logger
46
         MoveTo,
46
         MoveTo,
47
         EatEvent,
47
         EatEvent,
48
         AttackEvent,
48
         AttackEvent,
49
+        EatenEvent,
49
     ]
50
     ]
50
 
51
 
51
     def __init__(self, *args, **kwargs):
52
     def __init__(self, *args, **kwargs):

+ 4 - 1
sandbox/engulf/simulation.py View File

18
         super().remove(value)
18
         super().remove(value)
19
 
19
 
20
         if isinstance(value, Cell):
20
         if isinstance(value, Cell):
21
-            del self.cell_xyz[value.position]
21
+            try:
22
+                del self.cell_xyz[value.position]
23
+            except KeyError:
24
+                pass  # TODO: cE DICT DOIT CONTENIR DES LISTES DE SUBJECTS
22
 
25
 
23
         if isinstance(value, Grass):
26
         if isinstance(value, Grass):
24
             del self.grass_xyz[value.position]
27
             del self.grass_xyz[value.position]

+ 3 - 2
sandbox/engulf/subject.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
-from sandbox.engulf.behaviour import GrowUp, SearchGrass, EatGrass, Explore, CellBehaviourSelector, Hungry, Attack
2
+from sandbox.engulf.behaviour import GrowUp, SearchGrass, EatGrass, Explore, CellBehaviourSelector, Hungry, Attack, \
3
+    SearchPrey, EatPrey
3
 from sandbox.engulf.const import COLLECTION_CELL, COLLECTION_ALIVE, COLLECTION_EATABLE, COLLECTION_GRASS, \
4
 from sandbox.engulf.const import COLLECTION_CELL, COLLECTION_ALIVE, COLLECTION_EATABLE, COLLECTION_GRASS, \
4
     COLLECTION_PREY, COLLECTION_PREDATOR
5
     COLLECTION_PREY, COLLECTION_PREDATOR
5
 from synergine2.simulation import Subject
6
 from synergine2.simulation import Subject
44
 
45
 
45
 class PredatorCell(Cell):
46
 class PredatorCell(Cell):
46
     collections = Cell.collections[:] + [COLLECTION_PREDATOR]
47
     collections = Cell.collections[:] + [COLLECTION_PREDATOR]
47
-    behaviours_classes = Cell.behaviours_classes[:] + [Attack]
48
+    behaviours_classes = Cell.behaviours_classes[:] + [SearchPrey, Attack, EatPrey]
48
 
49
 
49
 
50
 
50
 class Grass(XYZSubjectMixin, Subject):
51
 class Grass(XYZSubjectMixin, Subject):

+ 3 - 0
synergine2/simulation.py View File

17
         config: Config,
17
         config: Config,
18
         simulation: 'Simulation',
18
         simulation: 'Simulation',
19
     ):
19
     ):
20
+        # TODO: Bannir les attribut de classe passé en reference ! Et meme virer les attr de classe tout court.
21
+        self.collections = self.collections[:]
22
+
20
         self.config = config
23
         self.config = config
21
         self.id = id(self)  # We store object id because it's lost between process
24
         self.id = id(self)  # We store object id because it's lost between process
22
         self.simulation = simulation
25
         self.simulation = simulation

+ 5 - 1
synergine2/xyz.py View File

216
 
216
 
217
     def remove(self, value: XYZSubjectMixin):
217
     def remove(self, value: XYZSubjectMixin):
218
         super().remove(value)
218
         super().remove(value)
219
-        del self.xyz[value.position]
219
+
220
+        try:
221
+            del self.xyz[value.position]
222
+        except KeyError:
223
+            pass  # TODO: cE DICT DOIT CONTENIR DES LISTES DE SUBJECTS
220
 
224
 
221
     def append(self, p_object: XYZSubjectMixin):
225
     def append(self, p_object: XYZSubjectMixin):
222
         super().append(p_object)
226
         super().append(p_object)