state.py 6.1KB

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