behaviour.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. # coding: utf-8
  2. import typing
  3. from random import choice
  4. from sandbox.engulf.const import COLLECTION_GRASS
  5. from synergine2.simulation import SubjectBehaviour, SimulationMechanism, SimulationBehaviour, SubjectBehaviourSelector
  6. from synergine2.simulation import Event
  7. from synergine2.utils import ChunkManager
  8. from synergine2.xyz import ProximitySubjectMechanism, DIRECTIONS, DIRECTION_SLIGHTLY, DIRECTION_FROM_NORTH_DEGREES, \
  9. get_direction_from_north_degree
  10. from synergine2.xyz_utils import get_around_positions_of_positions, get_around_positions_of, get_position_for_direction
  11. # Import for typing hint
  12. if False:
  13. from sandbox.engulf.subject import Grass
  14. class GrassGrownUp(Event):
  15. def __init__(self, subject_id, density, *args, **kwargs):
  16. super().__init__(*args, **kwargs)
  17. self.subject_id = subject_id
  18. self.density = density
  19. def repr_debug(self) -> str:
  20. return '{}: subject_id:{}, density:{}'.format(
  21. self.__class__.__name__,
  22. self.subject_id,
  23. self.density,
  24. )
  25. class GrassSpawn(Event):
  26. def __init__(self, subject_id, position, density, *args, **kwargs):
  27. super().__init__(*args, **kwargs)
  28. self.subject_id = subject_id
  29. self.position = position
  30. self.density = density
  31. def repr_debug(self) -> str:
  32. return '{}: subject_id:{}, position:{}, density:{}'.format(
  33. self.__class__.__name__,
  34. self.subject_id,
  35. self.position,
  36. self.density,
  37. )
  38. class GrassSpotablePositionsMechanism(SimulationMechanism):
  39. parallelizable = True
  40. def run(self, process_number: int=None, process_count: int=None):
  41. chunk_manager = ChunkManager(process_count)
  42. positions = list(self.simulation.subjects.grass_xyz.keys())
  43. positions_chunks = chunk_manager.make_chunks(positions)
  44. spotables = []
  45. for position in positions_chunks[process_number]:
  46. arounds = get_around_positions_of_positions(position)
  47. from_subject = self.simulation.subjects.grass_xyz[position]
  48. around_data = {
  49. 'from_subject': from_subject,
  50. 'around': [],
  51. }
  52. for around in arounds:
  53. if around not in self.simulation.subjects.grass_xyz:
  54. around_data['around'].append(around)
  55. if around_data['around']:
  56. spotables.append(around_data)
  57. return spotables
  58. class GrowUp(SubjectBehaviour):
  59. frequency = 20
  60. def run(self, data):
  61. return True
  62. def action(self, data) -> [Event]:
  63. self.subject.density += 1
  64. return [GrassGrownUp(
  65. self.subject.id,
  66. self.subject.density,
  67. )]
  68. class GrassSpawnBehaviour(SimulationBehaviour):
  69. frequency = 100
  70. use = [GrassSpotablePositionsMechanism]
  71. def run(self, data):
  72. spawns = []
  73. for around_data in data[GrassSpotablePositionsMechanism]:
  74. from_subject = around_data['from_subject']
  75. arounds = around_data['around']
  76. if from_subject.density >= 40:
  77. spawns.extend(arounds)
  78. return spawns
  79. @classmethod
  80. def merge_data(cls, new_data, start_data=None):
  81. start_data = start_data or []
  82. start_data.extend(new_data)
  83. return start_data
  84. def action(self, data) -> [Event]:
  85. from sandbox.engulf.subject import Grass # cyclic
  86. events = []
  87. for position in data:
  88. if position not in list(self.simulation.subjects.grass_xyz.keys()):
  89. new_grass = Grass(
  90. self.simulation,
  91. position=position,
  92. density=20,
  93. )
  94. self.simulation.subjects.append(new_grass)
  95. events.append(GrassSpawn(
  96. new_grass.id,
  97. new_grass.position,
  98. new_grass.density,
  99. ))
  100. return events
  101. class EatableDirectProximityMechanism(ProximitySubjectMechanism):
  102. distance = 1.41 # distance when on angle
  103. feel_collections = [COLLECTION_GRASS]
  104. def acceptable_subject(self, subject: 'Grass') -> bool:
  105. return subject.density >= self.config.simulation.eat_grass_required_density
  106. class MoveTo(Event):
  107. def __init__(self, subject_id: int, position: tuple, *args, **kwargs):
  108. super().__init__(*args, **kwargs)
  109. self.subject_id = subject_id
  110. self.position = position
  111. def repr_debug(self) -> str:
  112. return '{}: subject_id:{}, position:{}'.format(
  113. type(self).__name__,
  114. self.subject_id,
  115. self.position,
  116. )
  117. class EatEvent(Event):
  118. def __init__(self, eaten_id: int, eater_id: int, eaten_new_density: float, *args, **kwargs):
  119. super().__init__(*args, **kwargs)
  120. self.eaten_id = eaten_id
  121. self.eater_id = eater_id
  122. self.eaten_new_density = eaten_new_density
  123. class SearchFood(SubjectBehaviour):
  124. """
  125. Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
  126. """
  127. use = [EatableDirectProximityMechanism]
  128. def run(self, data):
  129. if self.subject.appetite < self.config.simulation.search_food_appetite_required:
  130. return False
  131. if not data[EatableDirectProximityMechanism]:
  132. return False
  133. direction_degrees = [d['direction'] for d in data[EatableDirectProximityMechanism]]
  134. return get_direction_from_north_degree(choice(direction_degrees))
  135. def action(self, data) -> [Event]:
  136. direction = data
  137. position = get_position_for_direction(self.subject.position, direction)
  138. self.subject.position = position
  139. self.subject.previous_direction = direction
  140. return [MoveTo(self.subject.id, position)]
  141. class Eat(SubjectBehaviour):
  142. """
  143. Prduit un immobilisme si sur une case de nourriture, dans le cas ou la cellule n'est as rassasié.
  144. """
  145. def run(self, data):
  146. if self.subject.appetite < self.config.simulation.eat_grass_required_density:
  147. return False
  148. for grass in self.simulation.collections.get(COLLECTION_GRASS, []):
  149. if grass.position == self.subject.position:
  150. return grass.id
  151. def action(self, data) -> [Event]:
  152. subject_id = data
  153. grass = self.simulation.subjects.index[subject_id] # TODO: cas ou grass disparu ?
  154. grass.density -= self.config.simulation.eat_grass_density_reduction
  155. self.subject.appetite -= self.config.simulation.eat_grass_appetite_reduction
  156. # TODO: Comment mettre des logs (ne peuvent pas être passé aux subprocess)?
  157. return [EatEvent(
  158. eater_id=self.subject.id,
  159. eaten_id=subject_id,
  160. eaten_new_density=grass.density,
  161. )]
  162. class Explore(SubjectBehaviour):
  163. """
  164. Produit un mouvement au hasard (ou un immobilisme)
  165. """
  166. use = []
  167. def run(self, data):
  168. return True # for now, want move every time
  169. def action(self, data) -> [Event]:
  170. direction = self.get_random_direction()
  171. position = get_position_for_direction(self.subject.position, direction)
  172. self.subject.position = position
  173. self.subject.previous_direction = direction
  174. return [MoveTo(self.subject.id, position)]
  175. def get_random_direction(self):
  176. if not self.subject.previous_direction:
  177. return choice(DIRECTIONS)
  178. return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
  179. class Hungry(SubjectBehaviour):
  180. def run(self, data):
  181. return True
  182. def action(self, data) -> [Event]:
  183. self.subject.appetite += self.config.simulation.hungry_reduction
  184. return []
  185. def get_random_direction(self):
  186. if not self.subject.previous_direction:
  187. return choice(DIRECTIONS)
  188. return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
  189. class CellBehaviourSelector(SubjectBehaviourSelector):
  190. # If behaviour in sublist, only one be kept in sublist
  191. behaviour_hierarchy = ( # TODO: refact it
  192. (
  193. Eat, # TODO: Introduce priority with appetite
  194. SearchFood, # TODO: Introduce priority with appetite
  195. Explore,
  196. ),
  197. )
  198. def reduce_behaviours(
  199. self,
  200. behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
  201. ) -> typing.Dict[typing.Type[SubjectBehaviour], object]:
  202. reduced_behaviours = {} # type: typing.Dict[typing.Type[SubjectBehaviour], object]
  203. for behaviour_class, behaviour_data in behaviours.items():
  204. if not self.behaviour_class_in_sublist(behaviour_class):
  205. reduced_behaviours[behaviour_class] = behaviour_data
  206. elif self.behaviour_class_is_prior(behaviour_class, behaviours):
  207. reduced_behaviours[behaviour_class] = behaviour_data
  208. return reduced_behaviours
  209. def behaviour_class_in_sublist(self, behaviour_class: typing.Type[SubjectBehaviour]) -> bool:
  210. for sublist in self.behaviour_hierarchy:
  211. if behaviour_class in sublist:
  212. return True
  213. return False
  214. def behaviour_class_is_prior(
  215. self,
  216. behaviour_class: typing.Type[SubjectBehaviour],
  217. behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
  218. ) -> bool:
  219. for sublist in self.behaviour_hierarchy:
  220. if behaviour_class in sublist:
  221. behaviour_position = sublist.index(behaviour_class)
  222. other_behaviour_top_position = self.get_other_behaviour_top_position(
  223. behaviour_class,
  224. behaviours,
  225. sublist,
  226. )
  227. if other_behaviour_top_position is not None and behaviour_position > other_behaviour_top_position:
  228. return False
  229. return True
  230. def get_other_behaviour_top_position(
  231. self,
  232. exclude_behaviour_class,
  233. behaviours,
  234. sublist,
  235. ) -> typing.Union[None, int]:
  236. position = None
  237. for behaviour_class in behaviours.keys():
  238. if behaviour_class != exclude_behaviour_class:
  239. try:
  240. behaviour_position = sublist.index(behaviour_class)
  241. if position is None or behaviour_position < position:
  242. position = behaviour_position
  243. except ValueError:
  244. pass
  245. return position