Browse Source

engulf: predator eat prey

Bastien Sevajol 7 years ago
parent
commit
7401fa64cd

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

@@ -2,7 +2,7 @@
2 2
 import typing
3 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 6
 from sandbox.engulf.exceptions import NotFoundWhereToGo
7 7
 from synergine2.simulation import SubjectBehaviour, SimulationMechanism, SimulationBehaviour, SubjectBehaviourSelector
8 8
 from synergine2.simulation import Event
@@ -169,6 +169,12 @@ class EatEvent(Event):
169 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 178
 class AttackEvent(Event):
173 179
     def __init__(self, attacker_id: int, attacked_id: int, *args, **kwargs):
174 180
         super().__init__(*args, **kwargs)
@@ -176,20 +182,20 @@ class AttackEvent(Event):
176 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 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 193
             return False
188 194
 
189
-        if not data[GrassEatableDirectProximityMechanism]:
195
+        if not data[self.mechanism_data_class]:
190 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 199
         return get_direction_from_north_degree(choice(direction_degrees))
194 200
 
195 201
     def action(self, data) -> [Event]:
@@ -201,10 +207,29 @@ class SearchGrass(SubjectBehaviour):
201 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 233
     def run(self, data):
209 234
         if self.subject.appetite < self.config.simulation.eat_grass_required_density:
210 235
             return False
@@ -231,6 +256,32 @@ class EatGrass(SubjectBehaviour):
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 285
 class Explore(SubjectBehaviour):
235 286
     """
236 287
     Produit un mouvement au hasard (ou un immobilisme)
@@ -238,7 +289,11 @@ class Explore(SubjectBehaviour):
238 289
     use = []
239 290
 
240 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 298
     def action(self, data) -> [Event]:
244 299
         try:
@@ -290,12 +345,30 @@ class Attack(SubjectBehaviour):
290 345
     use = [PreyEatableDirectProximityMechanism]
291 346
 
292 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 360
         return False
296 361
 
297 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 372
         return [AttackEvent(attacker_id=self.subject.id, attacked_id=data['subject'].id)]
300 373
 
301 374
 
@@ -303,7 +376,10 @@ class CellBehaviourSelector(SubjectBehaviourSelector):
303 376
     # If behaviour in sublist, only one be kept in sublist
304 377
     behaviour_hierarchy = (  # TODO: refact it
305 378
         (
379
+            EatPrey,
306 380
             EatGrass,  # TODO: Introduce priority with appetite
381
+            Attack,
382
+            SearchPrey,
307 383
             SearchGrass,  # TODO: Introduce priority with appetite
308 384
             Explore,
309 385
         ),

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

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

+ 19 - 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, EatEvent, AttackEvent
7
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo as MoveToEvent, EatEvent, AttackEvent, EatenEvent
8 8
 from sandbox.engulf.subject import Cell, Grass, PreyCell, PredatorCell
9 9
 from synergine2.terminals import TerminalPackage
10 10
 from synergine2_cocos2d.gui import Gui, GridLayerMixin
@@ -41,6 +41,9 @@ class CellsLayer(GridLayerMixin, BaseMainLayer):
41 41
         self.add(cell)
42 42
 
43 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 47
         cell = self.cell_ids[subject_id]
45 48
 
46 49
         window_position = self.grid_manager.get_window_position(position[0], position[1])
@@ -55,6 +58,12 @@ class CellsLayer(GridLayerMixin, BaseMainLayer):
55 58
         attacker = self.cell_ids[attacker_id]
56 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 68
 class GrassLayer(GridLayerMixin, BaseMainLayer):
60 69
     def __init__(self, game: 'Game', *args, **kwargs):
@@ -115,6 +124,10 @@ class Game(Gui):
115 124
             AttackEvent,
116 125
             self.on_attack,
117 126
         )
127
+        self.terminal.register_event_handler(
128
+            EatenEvent,
129
+            self.on_eaten,
130
+        )
118 131
 
119 132
     def get_main_scene(self):
120 133
         return self.main_scene
@@ -163,3 +176,8 @@ class Game(Gui):
163 176
             event.attacker_id,
164 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,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, MoveTo, EatEvent, AttackEvent
30
+from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, MoveTo, EatEvent, AttackEvent, EatenEvent
31 31
 
32 32
 from synergine2.config import Config
33 33
 from synergine2.log import get_default_logger
@@ -46,6 +46,7 @@ class GameTerminal(Terminal):
46 46
         MoveTo,
47 47
         EatEvent,
48 48
         AttackEvent,
49
+        EatenEvent,
49 50
     ]
50 51
 
51 52
     def __init__(self, *args, **kwargs):

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

@@ -18,7 +18,10 @@ class EngulfSubjects(XYZSubjects):
18 18
         super().remove(value)
19 19
 
20 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 26
         if isinstance(value, Grass):
24 27
             del self.grass_xyz[value.position]

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

@@ -1,5 +1,6 @@
1 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 4
 from sandbox.engulf.const import COLLECTION_CELL, COLLECTION_ALIVE, COLLECTION_EATABLE, COLLECTION_GRASS, \
4 5
     COLLECTION_PREY, COLLECTION_PREDATOR
5 6
 from synergine2.simulation import Subject
@@ -44,7 +45,7 @@ class PreyCell(Cell):
44 45
 
45 46
 class PredatorCell(Cell):
46 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 51
 class Grass(XYZSubjectMixin, Subject):

+ 3 - 0
synergine2/simulation.py View File

@@ -17,6 +17,9 @@ class Subject(object):
17 17
         config: Config,
18 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 23
         self.config = config
21 24
         self.id = id(self)  # We store object id because it's lost between process
22 25
         self.simulation = simulation

+ 5 - 1
synergine2/xyz.py View File

@@ -216,7 +216,11 @@ class XYZSubjects(Subjects):
216 216
 
217 217
     def remove(self, value: XYZSubjectMixin):
218 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 225
     def append(self, p_object: XYZSubjectMixin):
222 226
         super().append(p_object)