physics.py 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # coding: utf-8
  2. import typing
  3. from dijkstar import Graph
  4. from dijkstar import find_path
  5. from synergine2.config import Config
  6. from synergine2.share import shared
  7. from synergine2_xyz.map import TMXMap
  8. from synergine2_xyz.map import XYZTile
  9. from synergine2_xyz.subjects import XYZSubject
  10. from synergine2_xyz.tmx_utils import fill_matrix
  11. from synergine2_xyz.utils import get_line_xy_path
  12. from synergine2_xyz.xyz import get_neighbor_positions
  13. class MoveCostComputer(object):
  14. def __init__(
  15. self,
  16. config: Config,
  17. ) -> None:
  18. self.config = config
  19. def for_subject(self, subject: XYZSubject):
  20. # TODO: Verifier ce que sont les parametres pour les nommer correctement
  21. def move_cost_func(previous_node: str, next_node: str, tile: XYZTile, unknown):
  22. return self.compute_move_cost(subject, tile, previous_node, next_node, unknown)
  23. return move_cost_func
  24. def compute_move_cost(
  25. self,
  26. subject: XYZSubject,
  27. tile: XYZTile,
  28. previous_node: str,
  29. next_node: str,
  30. unknown,
  31. ) -> float:
  32. return 1.0
  33. class Matrixes(object):
  34. _matrixes = shared.create('matrixes', value=lambda: {}) # type: typing.Dict[str, typing.List[typing.List[tuple]]]
  35. _value_structures = shared.create('value_structures', value=lambda: {}) # type: typing.List[str]
  36. def initialize_empty_matrix(
  37. self,
  38. name: str,
  39. matrix_width: int,
  40. matrix_height: int,
  41. value_structure: typing.List[str],
  42. ) -> None:
  43. self._matrixes[name] = []
  44. self._value_structures[name] = value_structure
  45. for y in range(matrix_height):
  46. x_list = []
  47. for x in range(matrix_width):
  48. x_list.append(tuple([0.0] * len(value_structure)))
  49. self._matrixes[name].append(x_list)
  50. def get_matrix(self, name: str) -> typing.List[typing.List[tuple]]:
  51. return self._matrixes[name]
  52. def update_matrix(self, name: str, x: int, y: int, value: tuple) -> None:
  53. matrix = self.get_matrix(name)
  54. matrix[y][x] = value
  55. # TODO: Test if working and needed ? This is not perf friendly ...
  56. # Force shared data update
  57. self._matrixes = dict(self._matrixes)
  58. def get_path_positions(
  59. self,
  60. from_: typing.Tuple[int, int],
  61. to: typing.Tuple[int, int],
  62. ) -> typing.List[typing.Tuple[int, int]]:
  63. return get_line_xy_path(from_, to)
  64. def get_values_for_path(
  65. self,
  66. name: str,
  67. path_positions: typing.List[typing.Tuple[int, int]],
  68. value_name: str=None,
  69. ):
  70. values = []
  71. value_name_position = None
  72. if value_name:
  73. value_name_position = self._value_structures[name].index(value_name)
  74. matrix = self.get_matrix(name)
  75. for path_position in path_positions:
  76. x, y = path_position
  77. if value_name_position is None:
  78. values.append(matrix[y][x])
  79. else:
  80. values.append(matrix[y][x][value_name_position])
  81. return values
  82. def get_values_for_path_as_dict(
  83. self,
  84. name: str,
  85. path_positions: typing.List[typing.Tuple[int, int]],
  86. value_name: str=None,
  87. ) -> typing.Dict[int, typing.Dict[int, typing.Any]]:
  88. values_as_dict = {} # type: typing.Dict[int, typing.Dict[int, typing.Any]]
  89. values = self.get_values_for_path(name, path_positions, value_name)
  90. for position, value in zip(path_positions, values):
  91. values_as_dict.setdefault(position[0], {})[position[1]] = value
  92. return values_as_dict
  93. def get_value(self, matrix_name: str, x: int, y: int, value_name: str) -> float:
  94. matrix = self.get_matrix(matrix_name)
  95. values = matrix[y][x]
  96. value_position = self._value_structures[matrix_name].index(value_name)
  97. return values[value_position]
  98. class Physics(object):
  99. visibility_matrix = Matrixes
  100. move_cost_computer_class = MoveCostComputer
  101. def __init__(
  102. self,
  103. config: Config,
  104. ) -> None:
  105. self.config = config
  106. self.graph = Graph() # Graph of possible movements for dijkstar algorithm lib
  107. self.visibility_matrix = self.visibility_matrix()
  108. self.move_cost_computer = self.move_cost_computer_class(config)
  109. def load(self) -> None:
  110. pass
  111. def position_to_key(self, position: typing.Tuple[int, int]) -> str:
  112. return '{}.{}'.format(*position)
  113. def found_path(
  114. self,
  115. start: typing.Tuple[int, int],
  116. end: typing.Tuple[int, int],
  117. subject: XYZSubject,
  118. ) -> typing.List[typing.Tuple[int, int]]:
  119. start_key = self.position_to_key(start)
  120. end_key = self.position_to_key(end)
  121. found_path = find_path(self.graph, start_key, end_key, cost_func=self.move_cost_computer.for_subject(subject))
  122. regular_path = []
  123. for position in found_path[0][1:]:
  124. x, y = map(int, position.split('.'))
  125. regular_path.append((x, y))
  126. return regular_path
  127. class TMXPhysics(Physics):
  128. tmx_map_class = TMXMap
  129. matrixes_configuration = None # type: typing.Dict[str, typing.List[str]]
  130. def __init__(
  131. self,
  132. config: Config,
  133. map_file_path: str,
  134. matrixes: Matrixes=None,
  135. ) -> None:
  136. super().__init__(config)
  137. self.map_file_path = map_file_path
  138. self.tmx_map = self.tmx_map_class(map_file_path)
  139. self.matrixes = matrixes or Matrixes()
  140. def load(self) -> None:
  141. self.load_graph_from_map(self.map_file_path)
  142. self.load_matrixes_from_map()
  143. def load_graph_from_map(self, map_file_path: str) -> None:
  144. # TODO: tmx_map contient tout en cache, faire le dessous en exploitant tmx_map.
  145. for y in range(self.tmx_map.height):
  146. for x in range(self.tmx_map.width):
  147. position = self.position_to_key((x, y))
  148. for neighbor_position in get_neighbor_positions((x, y)):
  149. neighbor = '{}.{}'.format(*neighbor_position)
  150. neighbor_x, neighbor_y = neighbor_position
  151. if neighbor_x > self.tmx_map.width-1 or neighbor_x < 0:
  152. continue
  153. if neighbor_y > self.tmx_map.height-1 or neighbor_y < 0:
  154. continue
  155. # Note: movement consider future tile properties
  156. to_tile = self.tmx_map.layer_tiles('terrain')[neighbor]
  157. # Note: Voir https://pypi.python.org/pypi/Dijkstar/2.2
  158. self.graph.add_edge(position, neighbor, to_tile)
  159. def load_matrixes_from_map(self) -> None:
  160. if not self.matrixes_configuration:
  161. return
  162. for matrix_name, properties in self.matrixes_configuration.items():
  163. self.matrixes.initialize_empty_matrix(
  164. matrix_name,
  165. matrix_width=self.tmx_map.width,
  166. matrix_height=self.tmx_map.height,
  167. value_structure=properties,
  168. )
  169. fill_matrix(self.tmx_map, self.matrixes, 'terrain', matrix_name, properties)
  170. def subject_see_subject(self, observer: XYZSubject, observed: XYZSubject) -> bool:
  171. raise NotImplementedError()