Browse Source

Merge branch 'feature/dump_state'

Bastien Sevajol 5 years ago
parent
commit
8a2ccf2c75

+ 1 - 0
README.md View File

@@ -39,3 +39,4 @@ When unit selected:
39 39
 * C: crouch
40 40
 * M: move
41 41
 * F: fire (not implemented)
42
+* S: Save current state into OpenCombat dir (or dir specified with `--state-save-dir`)

+ 2 - 0
config.yaml View File

@@ -22,7 +22,9 @@ game:
22 22
 
23 23
 global:
24 24
     state_loader: "opencombat.state.StateLoader"
25
+    state_dumper: "opencombat.state.StateDumper"
25 26
     state_schema: "opencombat/state.xsd"
27
+    state_template: "opencombat/state_template.xml"
26 28
     cache_dir_path: 'cache'
27 29
     include_path:
28 30
       maps:

+ 84 - 6
maps/001/state1.xml View File

@@ -9,42 +9,120 @@
9 9
             <position>72,7</position>
10 10
             <direction>90</direction>
11 11
             <mode>standup</mode>
12
-            <side>URSS</side>
12
+            <properties>
13
+                <item>
14
+                    <key>SELECTION_COLOR_RGB</key>
15
+                    <value>204,0,0</value>
16
+                </item>
17
+                <item>
18
+                    <key>FLAG</key>
19
+                    <value>FLAG_URSS</value>
20
+                </item>
21
+                <item>
22
+                    <key>SIDE</key>
23
+                    <value>ALLIES</value>
24
+                </item>
25
+            </properties>
13 26
         </subject>
14 27
         <subject>
15 28
             <type>opencombat.simulation.subject.ManSubject</type>
16 29
             <position>72,9</position>
17 30
             <direction>90</direction>
18 31
             <mode>standup</mode>
19
-            <side>URSS</side>
32
+            <properties>
33
+                <item>
34
+                    <key>SELECTION_COLOR_RGB</key>
35
+                    <value>204,0,0</value>
36
+                </item>
37
+                <item>
38
+                    <key>FLAG</key>
39
+                    <value>FLAG_URSS</value>
40
+                </item>
41
+                <item>
42
+                    <key>SIDE</key>
43
+                    <value>ALLIES</value>
44
+                </item>
45
+            </properties>
20 46
         </subject>
21 47
         <subject>
22 48
             <type>opencombat.simulation.subject.ManSubject</type>
23 49
             <position>72,11</position>
24 50
             <direction>90</direction>
25 51
             <mode>standup</mode>
26
-            <side>URSS</side>
52
+            <properties>
53
+                <item>
54
+                    <key>SELECTION_COLOR_RGB</key>
55
+                    <value>204,0,0</value>
56
+                </item>
57
+                <item>
58
+                    <key>FLAG</key>
59
+                    <value>FLAG_URSS</value>
60
+                </item>
61
+                <item>
62
+                    <key>SIDE</key>
63
+                    <value>ALLIES</value>
64
+                </item>
65
+            </properties>
27 66
         </subject>
28 67
         <subject>
29 68
             <type>opencombat.simulation.subject.ManSubject</type>
30 69
             <position>58,54</position>
31 70
             <direction>270</direction>
32 71
             <mode>hiding</mode>
33
-            <side>DE</side>
72
+            <properties>
73
+                <item>
74
+                    <key>SELECTION_COLOR_RGB</key>
75
+                    <value>0,81,211</value>
76
+                </item>
77
+                <item>
78
+                    <key>FLAG</key>
79
+                    <value>FLAG_DE</value>
80
+                </item>
81
+                <item>
82
+                    <key>SIDE</key>
83
+                    <value>AXIS</value>
84
+                </item>
85
+            </properties>
34 86
         </subject>
35 87
         <subject>
36 88
             <type>opencombat.simulation.subject.ManSubject</type>
37 89
             <position>58,56</position>
38 90
             <direction>270</direction>
39 91
             <mode>hiding</mode>
40
-            <side>DE</side>
92
+            <properties>
93
+                <item>
94
+                    <key>SELECTION_COLOR_RGB</key>
95
+                    <value>0,81,211</value>
96
+                </item>
97
+                <item>
98
+                    <key>FLAG</key>
99
+                    <value>FLAG_DE</value>
100
+                </item>
101
+                <item>
102
+                    <key>SIDE</key>
103
+                    <value>AXIS</value>
104
+                </item>
105
+            </properties>
41 106
         </subject>
42 107
         <subject>
43 108
             <type>opencombat.simulation.subject.ManSubject</type>
44 109
             <position>58,58</position>
45 110
             <direction>270</direction>
46 111
             <mode>hiding</mode>
47
-            <side>DE</side>
112
+            <properties>
113
+                <item>
114
+                    <key>SELECTION_COLOR_RGB</key>
115
+                    <value>0,81,211</value>
116
+                </item>
117
+                <item>
118
+                    <key>FLAG</key>
119
+                    <value>FLAG_DE</value>
120
+                </item>
121
+                <item>
122
+                    <key>SIDE</key>
123
+                    <value>AXIS</value>
124
+                </item>
125
+            </properties>
48 126
         </subject>
49 127
     </subjects>
50 128
 </state>

+ 2 - 2
opencombat/const.py View File

@@ -3,8 +3,8 @@
3 3
 COLLECTION_ALIVE = 'ALIVE'
4 4
 
5 5
 FLAG = 'FLAG'
6
-FLAG_DE = 'DE'
7
-FLAG_URSS = 'URSS'
6
+FLAG_DE = 'FLAG_DE'
7
+FLAG_URSS = 'FLAG_URSS'
8 8
 
9 9
 SIDE = 'SIDE'
10 10
 COMBAT_MODE = 'COMBAT_MODE'

+ 16 - 0
opencombat/gui/base.py View File

@@ -21,6 +21,7 @@ from synergine2_cocos2d.util import PathManager
21 21
 from opencombat.gui.animation import ANIMATION_WALK
22 22
 from opencombat.gui.animation import ANIMATION_CRAWL
23 23
 from opencombat.gui.fire import GuiFiringEvent
24
+from opencombat.gui.state import SaveStateInteraction
24 25
 from opencombat.simulation.interior import InteriorManager
25 26
 from opencombat.simulation.tmx import TileMap
26 27
 from opencombat.user_action import UserAction
@@ -59,9 +60,20 @@ class EditLayer(BaseEditLayer):
59 60
             if k == key.F:
60 61
                 self.user_action_pending = UserAction.ORDER_FIRE
61 62
 
63
+        if k == key.S:
64
+            self.run_save_state()
65
+
62 66
     def draw(self) -> None:
63 67
         super().draw()
64 68
 
69
+    def run_save_state(self) -> None:
70
+        interaction = self.layer_manager\
71
+            .interaction_manager\
72
+            .get_for_user_action(
73
+                UserAction.SAVE_STATE,
74
+            )
75
+        interaction.execute()
76
+
65 77
 
66 78
 class BackgroundLayer(cocos.layer.Layer):
67 79
     def __init__(
@@ -272,6 +284,10 @@ class Game(TMXGui):
272 284
         self.layer_manager.interaction_manager.register(MoveFastActorInteraction, self.layer_manager)
273 285
         self.layer_manager.interaction_manager.register(MoveCrawlActorInteraction, self.layer_manager)
274 286
         self.layer_manager.interaction_manager.register(FireActorInteraction, self.layer_manager)
287
+        self.layer_manager.interaction_manager.register(
288
+            SaveStateInteraction,
289
+            self.layer_manager,
290
+        )
275 291
 
276 292
     def set_subject_position(
277 293
         self,

+ 18 - 0
opencombat/gui/state.py View File

@@ -0,0 +1,18 @@
1
+# coding: utf-8
2
+
3
+from synergine2.terminals import TerminalPackage
4
+from synergine2_cocos2d.interaction import Interaction
5
+
6
+from opencombat.simulation.state import SaveStateSimulationAction
7
+from opencombat.user_action import UserAction
8
+
9
+
10
+class SaveStateInteraction(Interaction):
11
+    gui_action = UserAction.SAVE_STATE
12
+
13
+    def get_package_for_terminal(self) -> TerminalPackage:
14
+        return TerminalPackage(
15
+            simulation_actions=[
16
+                (SaveStateSimulationAction, {}),
17
+            ]
18
+        )

+ 41 - 0
opencombat/simulation/state.py View File

@@ -0,0 +1,41 @@
1
+import os
2
+import time
3
+import typing
4
+
5
+from synergine2.config import Config
6
+from synergine2.simulation import SimulationBehaviour
7
+from synergine2.simulation import Event
8
+from synergine2.simulation import Simulation
9
+
10
+from opencombat.state import StateConstructorBuilder
11
+
12
+
13
+class SaveStateSimulationAction(SimulationBehaviour):
14
+    def __init__(
15
+        self,
16
+        config: Config,
17
+        simulation: Simulation,
18
+    ):
19
+        super().__init__(config, simulation)
20
+        self.state_dumper = StateConstructorBuilder(
21
+            config,
22
+            simulation,
23
+        ).get_state_dumper()
24
+        self.state_save_dir = self.config.resolve('_runtime.state_save_dir')
25
+
26
+    def run(self, data):
27
+        pass
28
+
29
+    def action(self, data) -> typing.List[Event]:
30
+        state_file_path = os.path.join(
31
+            self.state_save_dir,
32
+            'state_{}.xml'.format(time.time())
33
+        )
34
+        with open(state_file_path, 'w+') as file:
35
+            file.write(self.state_dumper.get_state_dump())
36
+
37
+        return []
38
+
39
+    @classmethod
40
+    def merge_data(cls, new_data, start_data=None):
41
+        pass

+ 120 - 25
opencombat/state.py View File

@@ -6,18 +6,14 @@ from lxml import etree
6 6
 
7 7
 from synergine2.config import Config
8 8
 from synergine2.log import get_logger
9
-from synergine2_cocos2d.const import SELECTION_COLOR_RGB
10 9
 
11 10
 from opencombat.exception import StateLoadError
11
+from opencombat.exception import NotFoundError
12 12
 from opencombat.simulation.base import TileStrategySimulation
13 13
 from opencombat.simulation.subject import TileSubject
14 14
 from opencombat.util import get_class_from_string_path
15
+from opencombat.util import pretty_xml
15 16
 from opencombat.util import get_text_xml_element
16
-from opencombat.const import FLAG, SIDE
17
-from opencombat.const import FLAG_DE
18
-from opencombat.const import DE_COLOR
19
-from opencombat.const import URSS_COLOR
20
-from opencombat.const import FLAG_URSS
21 17
 
22 18
 
23 19
 class State(object):
@@ -72,26 +68,109 @@ class State(object):
72 68
             get_text_xml_element(subject_element, 'direction'),
73 69
         )
74 70
 
75
-        side = get_text_xml_element(subject_element, 'side')
76
-        if side == 'DE':
77
-            subject_properties.update({
78
-                SELECTION_COLOR_RGB: DE_COLOR,
79
-                FLAG: FLAG_DE,
80
-                SIDE: 'AXIS',
81
-            })
82
-        elif side == 'URSS':
83
-            subject_properties.update({
84
-                SELECTION_COLOR_RGB: URSS_COLOR,
85
-                FLAG: FLAG_URSS,
86
-                SIDE: 'ALLIES',
87
-            })
88
-        else:
89
-            raise NotImplementedError('Don\'t know "{}" side'.format(
90
-                side,
91
-            ))
71
+        properties_element = subject_element.find('properties')
72
+        decode_properties_map = self._get_decode_properties_map()
73
+
74
+        for item_element in properties_element.findall('item'):
75
+            key_text = item_element.find('key').text
76
+            value_text = item_element.find('value').text
77
+
78
+            try:
79
+                decoded_value = decode_properties_map[key_text](value_text)
80
+            except KeyError:
81
+                raise NotFoundError(
82
+                    'You try to load property "{}" but it is unknown'.format(
83
+                        key_text,
84
+                    )
85
+                )
86
+
87
+            subject_properties[key_text] = decoded_value
92 88
 
93 89
         subject.properties = subject_properties
94 90
 
91
+    def _get_decode_properties_map(self) -> typing.Dict[str, typing.Callable[[str], typing.Any]]:  # nopep8
92
+        return {
93
+            'SELECTION_COLOR_RGB': lambda v: tuple(map(int, v.split(','))),
94
+            'FLAG': str,
95
+            'SIDE': str,
96
+        }
97
+
98
+
99
+class StateDumper(object):
100
+    def __init__(
101
+        self,
102
+        config: Config,
103
+        simulation: TileStrategySimulation,
104
+    ) -> None:
105
+        self._logger = get_logger('StateDumper', config)
106
+        self._config = config
107
+        self._simulation = simulation
108
+
109
+        state_template = self._config.resolve(
110
+           'global.state_template',
111
+           'opencombat/state_template.xml',
112
+        )
113
+        with open(state_template, 'r') as xml_file:
114
+            template_str = xml_file.read()
115
+
116
+        parser = etree.XMLParser(remove_blank_text=True)
117
+        self._state_root = etree.fromstring(
118
+            template_str.encode('utf-8'),
119
+            parser,
120
+        )
121
+        self._state_root_filled = False
122
+
123
+    def get_state_dump(self) -> str:
124
+        if not self._state_root_filled:
125
+            self._fill_state_root()
126
+
127
+        return pretty_xml(
128
+            etree.tostring(
129
+                self._state_root,
130
+            ).decode('utf-8'),
131
+        )
132
+
133
+    def _fill_state_root(self) -> None:
134
+        subjects_element = self._state_root.find('subjects')
135
+
136
+        for subject in self._simulation.subjects:
137
+            subject_element = etree.SubElement(subjects_element, 'subject')
138
+
139
+            position_element = etree.SubElement(subject_element, 'type')
140
+            position_element.text = '.'.join([
141
+                subject.__module__,
142
+                subject.__class__.__name__,
143
+            ])
144
+
145
+            position_element = etree.SubElement(subject_element, 'position')
146
+            position_element.text = ','.join(map(str, subject.position))
147
+
148
+            direction_element = etree.SubElement(subject_element, 'direction')
149
+            direction_element.text = str(subject.direction)
150
+
151
+            properties_element = etree.SubElement(
152
+                subject_element,
153
+                'properties',
154
+            )
155
+            encode_properties_map = self._get_encode_properties_map()
156
+
157
+            for key, value in subject.properties.items():
158
+                item_element = etree.SubElement(properties_element, 'item')
159
+                key_element = etree.SubElement(item_element, 'key')
160
+                value_element = etree.SubElement(item_element, 'value')
161
+
162
+                key_element.text = str(key)
163
+                value_element.text = encode_properties_map[key](value)
164
+
165
+        self._state_root_filled = True
166
+
167
+    def _get_encode_properties_map(self) -> typing.Dict[str, typing.Callable[[typing.Any], str]]:  # nopep8:
168
+        return {
169
+            'SELECTION_COLOR_RGB': lambda v: ','.join(map(str, v)),
170
+            'FLAG': str,
171
+            'SIDE': str,
172
+        }
173
+
95 174
 
96 175
 class StateLoader(object):
97 176
     def __init__(
@@ -178,13 +257,13 @@ class StateLoader(object):
178 257
         return doc
179 258
 
180 259
 
181
-class StateLoaderBuilder(object):
260
+class StateConstructorBuilder(object):
182 261
     def __init__(
183 262
         self,
184 263
         config: Config,
185 264
         simulation: TileStrategySimulation,
186 265
     ) -> None:
187
-        self._logger = get_logger('StateLoader', config)
266
+        self._logger = get_logger('StateConstructorBuilder', config)
188 267
         self._config = config
189 268
         self._simulation = simulation
190 269
 
@@ -203,3 +282,19 @@ class StateLoaderBuilder(object):
203 282
             self._config,
204 283
             self._simulation,
205 284
         )
285
+
286
+    def get_state_dumper(
287
+        self,
288
+    ) -> StateDumper:
289
+        class_address = self._config.resolve(
290
+            'global.state_dumper',
291
+            'opencombat.state.StateDumper',
292
+        )
293
+        state_loader_class = get_class_from_string_path(
294
+            self._config,
295
+            class_address,
296
+        )
297
+        return state_loader_class(
298
+            self._config,
299
+            self._simulation,
300
+        )

+ 13 - 8
opencombat/state.xsd View File

@@ -10,13 +10,18 @@
10 10
         </xs:restriction>
11 11
     </xs:simpleType>
12 12
 
13
-    <xs:simpleType name="sidetype" final="restriction">
14
-        <xs:restriction base="xs:string">
15
-            <xs:enumeration value="DE"/>
16
-            <xs:enumeration value="URSS"/>
17
-            <xs:enumeration value="US"/>
18
-        </xs:restriction>
19
-    </xs:simpleType>
13
+    <xs:complexType name="propertiestype">
14
+        <xs:sequence>
15
+            <xs:element maxOccurs="unbounded" name="item">
16
+                <xs:complexType>
17
+                    <xs:sequence>
18
+                        <xs:element name="key" type="xs:string"  />
19
+                        <xs:element name="value" type="xs:string" />
20
+                    </xs:sequence>
21
+                </xs:complexType>
22
+            </xs:element>
23
+        </xs:sequence>
24
+    </xs:complexType>
20 25
 
21 26
     <xs:simpleType name="modetype" final="restriction">
22 27
         <xs:restriction base="xs:string">
@@ -56,7 +61,7 @@
56 61
             <xs:element name="position" type="positiontype" minOccurs="1"/>
57 62
             <xs:element name="direction" type="directiontype" minOccurs="1"/>
58 63
             <xs:element name="mode" type="modetype" minOccurs="1"/>
59
-            <xs:element name="side" type="sidetype" minOccurs="1"/>
64
+            <xs:element name="properties" type="propertiestype" minOccurs="1"/>
60 65
         </xs:sequence>
61 66
     </xs:complexType>
62 67
 

+ 7 - 0
opencombat/state_template.xml View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<state type="before_battle">
3
+    <map>
4
+    </map>
5
+    <subjects>
6
+    </subjects>
7
+</state>

+ 1 - 0
opencombat/user_action.py View File

@@ -3,6 +3,7 @@ from synergine2_cocos2d.user_action import UserAction as BaseUserAction
3 3
 
4 4
 
5 5
 class UserAction(BaseUserAction):
6
+    SAVE_STATE = 'SAVE_STATE'
6 7
     ORDER_MOVE = 'ORDER_MOVE'
7 8
     ORDER_MOVE_FAST = 'ORDER_MOVE_FAST'
8 9
     ORDER_MOVE_CRAWL = 'ORDER_MOVE_CRAWL'

+ 17 - 0
opencombat/util.py View File

@@ -2,6 +2,8 @@
2 2
 import importlib
3 3
 
4 4
 from _elementtree import Element
5
+import xml.dom.minidom as md
6
+from io import StringIO
5 7
 
6 8
 from opencombat.exception import NotFoundError
7 9
 
@@ -38,3 +40,18 @@ def get_text_xml_element(
38 40
         return default_value
39 41
 
40 42
     return found.text
43
+
44
+
45
+def pretty_xml(xml_str):
46
+    """
47
+    Return a pretty xmlstr of given xml str. Thank's to:
48
+        https://gist.github.com/eliask/d8517790b11edac75983d1e6fdab3cab
49
+    :param xml_str: ugly xml str
50
+    :return: pretty xml str
51
+    """
52
+    indent = ' ' * 4
53
+    return '\n'.join(
54
+        line for line in
55
+        md.parse(StringIO(xml_str)).toprettyxml(indent=indent).split('\n')
56
+        if line.strip()
57
+    )

+ 18 - 3
run.py View File

@@ -12,7 +12,7 @@ from synergine2.terminals import TerminalManager
12 12
 
13 13
 from opencombat.simulation.base import TileStrategySimulation
14 14
 from opencombat.simulation.base import TileStrategySubjects
15
-from opencombat.state import StateLoaderBuilder
15
+from opencombat.state import StateConstructorBuilder
16 16
 from opencombat.terminal.base import CocosTerminal
17 17
 
18 18
 
@@ -20,12 +20,17 @@ def main(
20 20
     map_dir_path: str,
21 21
     seed_value: int=None,
22 22
     state_file_path: str=None,
23
+    state_save_dir: str='.',
23 24
 ):
24 25
     if seed_value is not None:
25 26
         seed(seed_value)
26 27
 
27 28
     config = Config()
28 29
     config.load_yaml('config.yaml')
30
+
31
+    # Runtime config
32
+    config.setdefault('_runtime', {})['state_save_dir'] = state_save_dir
33
+
29 34
     level = logging.getLevelName(config.resolve('global.logging_level', 'ERROR'))
30 35
     logger = get_default_logger(level=level)
31 36
 
@@ -35,7 +40,7 @@ def main(
35 40
     subjects = TileStrategySubjects(simulation=simulation)
36 41
 
37 42
     if state_file_path:
38
-        state_loader_builder = StateLoaderBuilder(config, simulation)
43
+        state_loader_builder = StateConstructorBuilder(config, simulation)
39 44
         state_loader = state_loader_builder.get_state_loader()
40 45
         state = state_loader.get_state(state_file_path)
41 46
         subjects.extend(state.subjects)
@@ -66,7 +71,17 @@ if __name__ == '__main__':
66 71
     parser.add_argument('map_dir_path', help='map directory path')
67 72
     parser.add_argument('--seed', dest='seed', default=None)
68 73
     parser.add_argument('--state', dest='state', default=None)
74
+    parser.add_argument(
75
+        '--state-save-dir',
76
+        dest='state_save_dir',
77
+        default='.',
78
+    )
69 79
 
70 80
     args = parser.parse_args()
71 81
 
72
-    main(args.map_dir_path, seed_value=args.seed, state_file_path=args.state)
82
+    main(
83
+        args.map_dir_path,
84
+        seed_value=args.seed,
85
+        state_file_path=args.state,
86
+        state_save_dir=args.state_save_dir,
87
+    )

+ 0 - 2
tests/fixtures/state_error_schema.xml View File

@@ -9,14 +9,12 @@
9 9
             <position>1,1</position>
10 10
             <direction>90</direction>
11 11
             <mode>standup</mode>
12
-            <side>URSS</side>
13 12
         </subject>
14 13
         <subjectFOO>
15 14
             <type>opencombat.simulation.subject.ManSubject</type>
16 15
             <position>10,10</position>
17 16
             <direction>270</direction>
18 17
             <mode>hiding</mode>
19
-            <side>DE</side>
20 18
         </subjectFOO>
21 19
     </subjects>
22 20
 </state>

+ 28 - 2
tests/fixtures/state_ok.xml View File

@@ -9,14 +9,40 @@
9 9
             <position>1,1</position>
10 10
             <direction>90</direction>
11 11
             <mode>standup</mode>
12
-            <side>URSS</side>
12
+            <properties>
13
+                <item>
14
+                    <key>SELECTION_COLOR_RGB</key>
15
+                    <value>204,0,0</value>
16
+                </item>
17
+                <item>
18
+                    <key>FLAG</key>
19
+                    <value>FLAG_URSS</value>
20
+                </item>
21
+                <item>
22
+                    <key>SIDE</key>
23
+                    <value>ALLIES</value>
24
+                </item>
25
+            </properties>
13 26
         </subject>
14 27
         <subject>
15 28
             <type>opencombat.simulation.subject.ManSubject</type>
16 29
             <position>10,10</position>
17 30
             <direction>270</direction>
18 31
             <mode>hiding</mode>
19
-            <side>DE</side>
32
+            <properties>
33
+                <item>
34
+                    <key>SELECTION_COLOR_RGB</key>
35
+                    <value>0,81,211</value>
36
+                </item>
37
+                <item>
38
+                    <key>FLAG</key>
39
+                    <value>FLAG_DE</value>
40
+                </item>
41
+                <item>
42
+                    <key>SIDE</key>
43
+                    <value>AXIS</value>
44
+                </item>
45
+            </properties>
20 46
         </subject>
21 47
     </subjects>
22 48
 </state>

+ 111 - 2
tests/test_state.py View File

@@ -1,10 +1,22 @@
1 1
 # coding: utf-8
2
+from collections import OrderedDict
3
+
2 4
 import pytest
3 5
 from synergine2.config import Config
6
+from synergine2_cocos2d.const import SELECTION_COLOR_RGB
4 7
 
5 8
 from opencombat.exception import StateLoadError
9
+from opencombat.simulation.base import TileStrategySimulation
10
+from opencombat.simulation.base import TileStrategySubjects
6 11
 from opencombat.simulation.subject import ManSubject
7
-from opencombat.state import StateLoaderBuilder, StateLoader
12
+from opencombat.state import StateConstructorBuilder, StateDumper
13
+from opencombat.state import StateLoader
14
+from opencombat.const import FLAG
15
+from opencombat.const import SIDE
16
+from opencombat.const import FLAG_DE
17
+from opencombat.const import DE_COLOR
18
+from opencombat.const import URSS_COLOR
19
+from opencombat.const import FLAG_URSS
8 20
 
9 21
 
10 22
 class MyStateLoader(StateLoader):
@@ -16,6 +28,39 @@ def state_loader(config, simulation):
16 28
     return StateLoader(config, simulation)
17 29
 
18 30
 
31
+@pytest.fixture
32
+def simulation_for_dump(config) -> TileStrategySimulation:
33
+    simulation = TileStrategySimulation(
34
+        config,
35
+        'tests/fixtures/map_a/map_a.tmx',
36
+    )
37
+    subjects = TileStrategySubjects(simulation=simulation)
38
+    simulation.subjects = subjects
39
+
40
+    man1 = ManSubject(config, simulation)
41
+    man1.position = (10, 11)
42
+    man1.direction = 42
43
+    man1.properties = OrderedDict([
44
+        (SELECTION_COLOR_RGB, DE_COLOR),
45
+        (FLAG, FLAG_DE),
46
+        (SIDE, 'AXIS'),
47
+    ])
48
+
49
+    man2 = ManSubject(config, simulation)
50
+    man2.position = (16, 8)
51
+    man2.direction = 197
52
+    man2.properties = OrderedDict([
53
+        (SELECTION_COLOR_RGB, URSS_COLOR),
54
+        (FLAG, FLAG_URSS),
55
+        (SIDE, 'ALLIES'),
56
+    ])
57
+
58
+    subjects.append(man1)
59
+    subjects.append(man2)
60
+
61
+    return simulation
62
+
63
+
19 64
 def test_state_loader_builder__ok__nominal_case(
20 65
     simulation,
21 66
 ):
@@ -24,7 +69,7 @@ def test_state_loader_builder__ok__nominal_case(
24 69
             'state_loader': 'tests.test_state.MyStateLoader',
25 70
         }
26 71
     })
27
-    builder = StateLoaderBuilder(config, simulation)
72
+    builder = StateConstructorBuilder(config, simulation)
28 73
     state_loader = builder.get_state_loader()
29 74
     assert type(state_loader) == MyStateLoader
30 75
 
@@ -64,3 +109,67 @@ def test_state__ok__subjects(
64 109
     assert (10, 10) == state.subjects[1].position
65 110
     assert 90.0 == state.subjects[0].direction
66 111
     assert 270.0 == state.subjects[1].direction
112
+
113
+    assert {
114
+               'SELECTION_COLOR_RGB': (204, 0, 0),
115
+               'FLAG': 'FLAG_URSS',
116
+               'SIDE': 'ALLIES',
117
+           } == state.subjects[0].properties
118
+    assert {
119
+               'SELECTION_COLOR_RGB': (0, 81, 211),
120
+               'FLAG': 'FLAG_DE',
121
+               'SIDE': 'AXIS',
122
+           } == state.subjects[1].properties
123
+
124
+
125
+def test_state__ok__dump(
126
+    config: Config,
127
+    simulation_for_dump: TileStrategySimulation,
128
+):
129
+    state_dumper = StateDumper(config, simulation_for_dump)
130
+    state_xml_str = state_dumper.get_state_dump()
131
+    assert """<?xml version="1.0" ?>
132
+<state type="before_battle">
133
+    <map>
134
+    </map>
135
+    <subjects>
136
+        <subject>
137
+            <type>opencombat.simulation.subject.ManSubject</type>
138
+            <position>10,11</position>
139
+            <direction>42</direction>
140
+            <properties>
141
+                <item>
142
+                    <key>SELECTION_COLOR_RGB</key>
143
+                    <value>0,81,211</value>
144
+                </item>
145
+                <item>
146
+                    <key>FLAG</key>
147
+                    <value>FLAG_DE</value>
148
+                </item>
149
+                <item>
150
+                    <key>SIDE</key>
151
+                    <value>AXIS</value>
152
+                </item>
153
+            </properties>
154
+        </subject>
155
+        <subject>
156
+            <type>opencombat.simulation.subject.ManSubject</type>
157
+            <position>16,8</position>
158
+            <direction>197</direction>
159
+            <properties>
160
+                <item>
161
+                    <key>SELECTION_COLOR_RGB</key>
162
+                    <value>204,0,0</value>
163
+                </item>
164
+                <item>
165
+                    <key>FLAG</key>
166
+                    <value>FLAG_URSS</value>
167
+                </item>
168
+                <item>
169
+                    <key>SIDE</key>
170
+                    <value>ALLIES</value>
171
+                </item>
172
+            </properties>
173
+        </subject>
174
+    </subjects>
175
+</state>""" == state_xml_str