state.py 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. # coding: utf-8
  2. import typing
  3. from _elementtree import Element
  4. from lxml import etree
  5. from synergine2.config import Config
  6. from synergine2.log import get_logger
  7. from synergine2_cocos2d.const import SELECTION_COLOR_RGB
  8. from opencombat.exception import StateLoadError
  9. from opencombat.simulation.base import TileStrategySimulation
  10. from opencombat.simulation.subject import TileSubject
  11. from opencombat.util import get_class_from_string_path
  12. from opencombat.util import get_text_xml_element
  13. from opencombat.const import FLAG
  14. from opencombat.const import SIDE
  15. from opencombat.const import FLAG_DE
  16. from opencombat.const import DE_COLOR
  17. from opencombat.const import URSS_COLOR
  18. from opencombat.const import FLAG_URSS
  19. class State(object):
  20. def __init__(
  21. self,
  22. config: Config,
  23. state_root: Element,
  24. simulation: TileStrategySimulation,
  25. ) -> None:
  26. self._config = config
  27. self._state_root = state_root
  28. self._subjects = None # type: typing.List[TileSubject]
  29. self._simulation = simulation
  30. @property
  31. def subjects(self) -> typing.List[TileSubject]:
  32. if self._subjects is None:
  33. self._subjects = self._get_subjects()
  34. return self._subjects
  35. def _get_subjects(self) -> typing.List[TileSubject]:
  36. subjects = []
  37. subject_elements = self._state_root.find('subjects').findall('subject')
  38. for subject_element in subject_elements:
  39. subject_class_path = subject_element.find('type').text
  40. subject_class = get_class_from_string_path(
  41. self._config,
  42. subject_class_path,
  43. )
  44. subject = subject_class(self._config, self._simulation)
  45. self._fill_subject(subject, subject_element)
  46. # TODO BS 2018-06-13: Fill subject with property
  47. subjects.append(subject)
  48. return subjects
  49. def _fill_subject(
  50. self,
  51. subject: TileSubject,
  52. subject_element: Element,
  53. ) -> None:
  54. subject_properties = {}
  55. subject.position = tuple(
  56. map(
  57. int,
  58. get_text_xml_element(subject_element, 'position').split(','),
  59. ),
  60. )
  61. subject.direction = float(
  62. get_text_xml_element(subject_element, 'direction'),
  63. )
  64. # FIXME BS 218-06-14: There is a confusion: don't use side but
  65. # "team". It probably need change in other code.
  66. side = get_text_xml_element(subject_element, 'side')
  67. if side == 'DE':
  68. subject_properties.update({
  69. SELECTION_COLOR_RGB: DE_COLOR,
  70. FLAG: FLAG_DE,
  71. SIDE: 'AXIS',
  72. })
  73. elif side == 'URSS':
  74. subject_properties.update({
  75. SELECTION_COLOR_RGB: URSS_COLOR,
  76. FLAG: FLAG_URSS,
  77. SIDE: 'ALLIES',
  78. })
  79. else:
  80. raise NotImplementedError('Don\'t know "{}" side'.format(
  81. side,
  82. ))
  83. subject.properties = subject_properties
  84. class StateDumper(object):
  85. def __init__(
  86. self,
  87. config: Config,
  88. simulation: TileStrategySimulation,
  89. ) -> None:
  90. self._logger = get_logger('StateDumper', config)
  91. self._config = config
  92. self._simulation = simulation
  93. state_template = self._config.resolve(
  94. 'global.state_template',
  95. 'opencombat/state_template.xml',
  96. )
  97. with open(state_template, 'r') as xml_file:
  98. template_str = xml_file.read()
  99. self._state_root = etree.fromstring(template_str.encode('utf-8'))
  100. def get_state_dump(self) -> Element:
  101. # TODO BS 2018-06-14: Code here xml construction
  102. return self._state_root
  103. class StateLoader(object):
  104. def __init__(
  105. self,
  106. config: Config,
  107. simulation: TileStrategySimulation,
  108. ) -> None:
  109. self._logger = get_logger('StateLoader', config)
  110. self._config = config
  111. self._simulation = simulation
  112. def get_state(
  113. self,
  114. state_file_path: str,
  115. ) -> State:
  116. return State(
  117. self._config,
  118. self._validate_and_return_state_element(state_file_path),
  119. self._simulation,
  120. )
  121. def _validate_and_return_state_element(
  122. self,
  123. state_file_path: str,
  124. ) -> Element:
  125. # open and read schema file
  126. schema_file_path = self._config.get(
  127. 'global.state_schema',
  128. 'opencombat/state.xsd',
  129. )
  130. with open(schema_file_path, 'r') as schema_file:
  131. schema_to_check = schema_file.read()
  132. # open and read xml file
  133. with open(state_file_path, 'r') as xml_file:
  134. xml_to_check = xml_file.read()
  135. xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
  136. xmlschema = etree.XMLSchema(xmlschema_doc)
  137. try:
  138. doc = etree.fromstring(xml_to_check.encode('utf-8'))
  139. # check for file IO error
  140. except IOError as exc:
  141. self._logger.error(exc)
  142. raise StateLoadError('Invalid File "{}": {}'.format(
  143. state_file_path,
  144. str(exc),
  145. ))
  146. # check for XML syntax errors
  147. except etree.XMLSyntaxError as exc:
  148. self._logger.error(exc)
  149. raise StateLoadError('XML Syntax Error in "{}": {}'.format(
  150. state_file_path,
  151. str(exc.error_log),
  152. ))
  153. except Exception as exc:
  154. self._logger.error(exc)
  155. raise StateLoadError('Unknown error with "{}": {}'.format(
  156. state_file_path,
  157. str(exc),
  158. ))
  159. # validate against schema
  160. try:
  161. xmlschema.assertValid(doc)
  162. except etree.DocumentInvalid as exc:
  163. self._logger.error(exc)
  164. raise StateLoadError(
  165. 'Schema validation error with "{}": {}'.format(
  166. state_file_path,
  167. str(exc),
  168. )
  169. )
  170. except Exception as exc:
  171. self._logger.error(exc)
  172. raise StateLoadError(
  173. 'Unknown validation error with "{}": {}'.format(
  174. state_file_path,
  175. str(exc),
  176. )
  177. )
  178. return doc
  179. class StateLoaderBuilder(object):
  180. def __init__(
  181. self,
  182. config: Config,
  183. simulation: TileStrategySimulation,
  184. ) -> None:
  185. self._logger = get_logger('StateLoader', config)
  186. self._config = config
  187. self._simulation = simulation
  188. def get_state_loader(
  189. self,
  190. ) -> StateLoader:
  191. class_address = self._config.resolve(
  192. 'global.state_loader',
  193. 'opencombat.state.StateLoader',
  194. )
  195. state_loader_class = get_class_from_string_path(
  196. self._config,
  197. class_address,
  198. )
  199. return state_loader_class(
  200. self._config,
  201. self._simulation,
  202. )