state.py 6.0KB

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