behaviour.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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. config=self.config,
  91. simulation=self.simulation,
  92. position=position,
  93. density=20,
  94. )
  95. self.simulation.subjects.append(new_grass)
  96. events.append(GrassSpawn(
  97. new_grass.id,
  98. new_grass.position,
  99. new_grass.density,
  100. ))
  101. return events
  102. class EatableDirectProximityMechanism(ProximitySubjectMechanism):
  103. distance = 1.41 # distance when on angle
  104. feel_collections = [COLLECTION_GRASS]
  105. def acceptable_subject(self, subject: 'Grass') -> bool:
  106. return subject.density >= self.config.simulation.eat_grass_required_density
  107. class MoveTo(Event):
  108. def __init__(self, subject_id: int, position: tuple, *args, **kwargs):
  109. super().__init__(*args, **kwargs)
  110. self.subject_id = subject_id
  111. self.position = position
  112. def repr_debug(self) -> str:
  113. return '{}: subject_id:{}, position:{}'.format(
  114. type(self).__name__,
  115. self.subject_id,
  116. self.position,
  117. )
  118. class EatEvent(Event):
  119. def __init__(self, eaten_id: int, eater_id: int, eaten_new_density: float, *args, **kwargs):
  120. super().__init__(*args, **kwargs)
  121. self.eaten_id = eaten_id
  122. self.eater_id = eater_id
  123. self.eaten_new_density = eaten_new_density
  124. class SearchFood(SubjectBehaviour):
  125. """
  126. Si une nourriture a une case de distance et cellule non rassasié, move dans sa direction.
  127. """
  128. use = [EatableDirectProximityMechanism]
  129. def run(self, data):
  130. if self.subject.appetite < self.config.simulation.search_food_appetite_required:
  131. return False
  132. if not data[EatableDirectProximityMechanism]:
  133. return False
  134. direction_degrees = [d['direction'] for d in data[EatableDirectProximityMechanism]]
  135. return get_direction_from_north_degree(choice(direction_degrees))
  136. def action(self, data) -> [Event]:
  137. direction = data
  138. position = get_position_for_direction(self.subject.position, direction)
  139. self.subject.position = position
  140. self.subject.previous_direction = direction
  141. return [MoveTo(self.subject.id, position)]
  142. class Eat(SubjectBehaviour):
  143. """
  144. Prduit un immobilisme si sur une case de nourriture, dans le cas ou la cellule n'est as rassasié.
  145. """
  146. def run(self, data):
  147. if self.subject.appetite < self.config.simulation.eat_grass_required_density:
  148. return False
  149. for grass in self.simulation.collections.get(COLLECTION_GRASS, []):
  150. if grass.position == self.subject.position:
  151. return grass.id
  152. def action(self, data) -> [Event]:
  153. subject_id = data
  154. grass = self.simulation.subjects.index[subject_id] # TODO: cas ou grass disparu ?
  155. grass.density -= self.config.simulation.eat_grass_density_reduction
  156. self.subject.appetite -= self.config.simulation.eat_grass_appetite_reduction
  157. # TODO: Comment mettre des logs (ne peuvent pas être passé aux subprocess)?
  158. return [EatEvent(
  159. eater_id=self.subject.id,
  160. eaten_id=subject_id,
  161. eaten_new_density=grass.density,
  162. )]
  163. class Explore(SubjectBehaviour):
  164. """
  165. Produit un mouvement au hasard (ou un immobilisme)
  166. """
  167. use = []
  168. def run(self, data):
  169. return True # for now, want move every time
  170. def action(self, data) -> [Event]:
  171. direction = self.get_random_direction()
  172. position = get_position_for_direction(self.subject.position, direction)
  173. self.subject.position = position
  174. self.subject.previous_direction = direction
  175. return [MoveTo(self.subject.id, position)]
  176. def get_random_direction(self):
  177. if not self.subject.previous_direction:
  178. return choice(DIRECTIONS)
  179. return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
  180. class Hungry(SubjectBehaviour):
  181. def run(self, data):
  182. return True
  183. def action(self, data) -> [Event]:
  184. self.subject.appetite += self.config.simulation.hungry_reduction
  185. return []
  186. def get_random_direction(self):
  187. if not self.subject.previous_direction:
  188. return choice(DIRECTIONS)
  189. return choice(DIRECTION_SLIGHTLY[self.subject.previous_direction])
  190. class CellBehaviourSelector(SubjectBehaviourSelector):
  191. # If behaviour in sublist, only one be kept in sublist
  192. behaviour_hierarchy = ( # TODO: refact it
  193. (
  194. Eat, # TODO: Introduce priority with appetite
  195. SearchFood, # TODO: Introduce priority with appetite
  196. Explore,
  197. ),
  198. )
  199. def reduce_behaviours(
  200. self,
  201. behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
  202. ) -> typing.Dict[typing.Type[SubjectBehaviour], object]:
  203. reduced_behaviours = {} # type: typing.Dict[typing.Type[SubjectBehaviour], object]
  204. for behaviour_class, behaviour_data in behaviours.items():
  205. if not self.behaviour_class_in_sublist(behaviour_class):
  206. reduced_behaviours[behaviour_class] = behaviour_data
  207. elif self.behaviour_class_is_prior(behaviour_class, behaviours):
  208. reduced_behaviours[behaviour_class] = behaviour_data
  209. return reduced_behaviours
  210. def behaviour_class_in_sublist(self, behaviour_class: typing.Type[SubjectBehaviour]) -> bool:
  211. for sublist in self.behaviour_hierarchy:
  212. if behaviour_class in sublist:
  213. return True
  214. return False
  215. def behaviour_class_is_prior(
  216. self,
  217. behaviour_class: typing.Type[SubjectBehaviour],
  218. behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
  219. ) -> bool:
  220. for sublist in self.behaviour_hierarchy:
  221. if behaviour_class in sublist:
  222. behaviour_position = sublist.index(behaviour_class)
  223. other_behaviour_top_position = self.get_other_behaviour_top_position(
  224. behaviour_class,
  225. behaviours,
  226. sublist,
  227. )
  228. if other_behaviour_top_position is not None and behaviour_position > other_behaviour_top_position:
  229. return False
  230. return True
  231. def get_other_behaviour_top_position(
  232. self,
  233. exclude_behaviour_class,
  234. behaviours,
  235. sublist,
  236. ) -> typing.Union[None, int]:
  237. position = None
  238. for behaviour_class in behaviours.keys():
  239. if behaviour_class != exclude_behaviour_class:
  240. try:
  241. behaviour_position = sublist.index(behaviour_class)
  242. if position is None or behaviour_position < position:
  243. position = behaviour_position
  244. except ValueError:
  245. pass
  246. return position