Browse Source

Issue #46: write xml file and xsd, begin state loader

Bastien Sevajol 10 months ago
parent
commit
ba34616dc2
7 changed files with 256 additions and 38 deletions
  1. 2 0
      config.yaml
  2. 22 0
      maps/001/state1.xml
  3. 4 0
      opencombat/exception.py
  4. 110 0
      opencombat/state.py
  5. 69 0
      opencombat/state.xsd
  6. 1 0
      requirements.txt
  7. 48 38
      run.py

+ 2 - 0
config.yaml View File

@@ -21,6 +21,8 @@ game:
21 21
       draw_interior_gap: 2
22 22
 
23 23
 global:
24
+    state_loader: "opencombat.state.StateLoader"
25
+    state_schema: "opencombat/state.xsd"
24 26
     cache_dir_path: 'cache'
25 27
     include_path:
26 28
       maps:

+ 22 - 0
maps/001/state1.xml View File

@@ -0,0 +1,22 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<state type="before_battle">
3
+    <map>
4
+        <name>001</name>
5
+    </map>
6
+    <subjects>
7
+        <subject>
8
+            <type>opencombat.simulation.subject.ManSubject</type>
9
+            <position>1,1</position>
10
+            <direction>90</direction>
11
+            <mode>standup</mode>
12
+            <side>URSS</side>
13
+        </subject>
14
+        <subject>
15
+            <type>opencombat.simulation.subject.ManSubject</type>
16
+            <position>10,10</position>
17
+            <direction>270</direction>
18
+            <mode>hiding</mode>
19
+            <side>DE</side>
20
+        </subject>
21
+    </subjects>
22
+</state>

+ 4 - 0
opencombat/exception.py View File

@@ -15,3 +15,7 @@ class UnknownFiringAnimation(OpenCombatException):
15 15
 
16 16
 class WrongMode(OpenCombatException):
17 17
     pass
18
+
19
+
20
+class StateLoadError(OpenCombatException):
21
+    pass

+ 110 - 0
opencombat/state.py View File

@@ -0,0 +1,110 @@
1
+# coding: utf-8
2
+import importlib
3
+import typing
4
+from io import StringIO
5
+import sys
6
+
7
+from lxml import etree
8
+
9
+from synergine2.config import Config
10
+from synergine2.log import get_logger
11
+from synergine2.simulation import Subject
12
+
13
+from opencombat.exception import StateLoadError
14
+
15
+
16
+class StateLoader(object):
17
+    def __init__(
18
+        self,
19
+        config: Config,
20
+        state_file_path: str,
21
+    ) -> None:
22
+        self.logger = get_logger('StateLoader', config)
23
+        self.config = config
24
+        self.state_file_path = state_file_path
25
+        self._validate()
26
+
27
+    def _validate(self) -> None:
28
+        # open and read schema file
29
+        schema_file_path = self.config.get(
30
+            'global.state_schema',
31
+            'opencombat/state.xsd',
32
+        )
33
+        with open(schema_file_path, 'r') as schema_file:
34
+            schema_to_check = schema_file.read()
35
+
36
+        # open and read xml file
37
+        with open(self.state_file_path, 'r') as xml_file:
38
+            xml_to_check = xml_file.read()
39
+
40
+        xmlschema_doc = etree.parse(StringIO(schema_to_check))
41
+        xmlschema = etree.XMLSchema(xmlschema_doc)
42
+
43
+        try:
44
+            doc = etree.parse(StringIO(xml_to_check))
45
+        # check for file IO error
46
+        except IOError as exc:
47
+            self.logger.error(exc)
48
+            raise StateLoadError('Invalid File "{}": {}'.format(
49
+                self.state_file_path,
50
+                str(exc),
51
+            ))
52
+        # check for XML syntax errors
53
+        except etree.XMLSyntaxError as exc:
54
+            self.logger.error(exc)
55
+            raise StateLoadError('XML Syntax Error in "{}": {}'.format(
56
+                self.state_file_path,
57
+                str(exc.error_log),
58
+            ))
59
+        except Exception as exc:
60
+            self.logger.error(exc)
61
+            raise StateLoadError('Unknown error with "{}": {}'.format(
62
+                self.state_file_path,
63
+                str(exc),
64
+            ))
65
+
66
+        # validate against schema
67
+        try:
68
+            xmlschema.assertValid(doc)
69
+        except etree.DocumentInvalid as exc:
70
+            self.logger.error(exc)
71
+            raise StateLoadError(
72
+                'Schema validation error with "{}": {}'.format(
73
+                    self.state_file_path,
74
+                    str(exc),
75
+                )
76
+            )
77
+        except Exception as exc:
78
+            self.logger.error(exc)
79
+            raise StateLoadError(
80
+                'Unknown validation error with "{}": {}'.format(
81
+                    self.state_file_path,
82
+                    str(exc),
83
+                )
84
+            )
85
+
86
+    def get_subjects(self) -> typing.List[Subject]:
87
+        raise NotImplementedError('TODO')
88
+
89
+
90
+class StateLoaderBuilder(object):
91
+    def __init__(
92
+        self,
93
+        config: Config,
94
+    ) -> None:
95
+        self.logger = get_logger('StateLoader', config)
96
+        self.config = config
97
+
98
+    def get_state_loader(
99
+        self,
100
+        state_file_path: str,
101
+    ) -> StateLoader:
102
+        class_address = self.config.get(
103
+            'global.state_loader',
104
+            'opencombat.state.StateLoader',
105
+        )
106
+        module_address = '.'.join(class_address.split('.')[:-1])
107
+        class_name = class_address.split('.')[-1]
108
+        module_ = importlib.import_module(module_address)
109
+        state_loader_class = getattr(module_, class_name)
110
+        return state_loader_class(self.config, state_file_path)

+ 69 - 0
opencombat/state.xsd View File

@@ -0,0 +1,69 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+
4
+    <xs:element name="state" type="statetype"/>
5
+
6
+    <xs:simpleType name="statetypetype" final="restriction">
7
+        <xs:restriction base="xs:string">
8
+            <xs:enumeration value="before_battle"/>
9
+            <xs:enumeration value="during_battle"/>
10
+        </xs:restriction>
11
+    </xs:simpleType>
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>
20
+
21
+    <xs:simpleType name="modetype" final="restriction">
22
+        <xs:restriction base="xs:string">
23
+            <xs:enumeration value="standup"/>
24
+            <xs:enumeration value="defending"/>
25
+            <xs:enumeration value="hiding"/>
26
+        </xs:restriction>
27
+    </xs:simpleType>
28
+
29
+    <xs:simpleType name="positiontype">
30
+        <xs:restriction base="xs:string">
31
+            <xs:pattern value="[0-9]+,[0-9]+"/>
32
+        </xs:restriction>
33
+    </xs:simpleType>
34
+
35
+    <xs:simpleType name="directiontype">
36
+        <xs:restriction base="xs:positiveInteger"/>
37
+    </xs:simpleType>
38
+
39
+    <xs:complexType name="statetype">
40
+        <xs:sequence>
41
+            <xs:element name="map" type="maptype" maxOccurs="1"/>
42
+            <xs:element name="subjects" type="subjectstype"/>
43
+        </xs:sequence>
44
+        <xs:attribute name="type" type="statetypetype" use="required"/>
45
+    </xs:complexType>
46
+
47
+    <xs:complexType name="subjectstype">
48
+        <xs:sequence>
49
+            <xs:element name="subject" type="subjecttype" maxOccurs="unbounded"/>
50
+        </xs:sequence>
51
+    </xs:complexType>
52
+
53
+    <xs:complexType name="subjecttype">
54
+        <xs:sequence>
55
+            <xs:element name="type" type="xs:string"/>
56
+            <xs:element name="position" type="positiontype"/>
57
+            <xs:element name="direction" type="directiontype"/>
58
+            <xs:element name="mode" type="modetype"/>
59
+            <xs:element name="side" type="sidetype"/>
60
+        </xs:sequence>
61
+    </xs:complexType>
62
+
63
+    <xs:complexType name="maptype">
64
+        <xs:sequence>
65
+            <xs:element name="name" type="xs:string"/>
66
+        </xs:sequence>
67
+    </xs:complexType>
68
+
69
+</xs:schema>

+ 1 - 0
requirements.txt View File

@@ -2,6 +2,7 @@ attrs==17.3.0
2 2
 Dijkstar==2.3.0
3 3
 freezegun==0.3.9
4 4
 future==0.16.0
5
+lxml==4.2.1
5 6
 numpy==1.13.3
6 7
 pluggy==0.6.0
7 8
 psutil==5.4.1

+ 48 - 38
run.py View File

@@ -20,10 +20,15 @@ from opencombat.simulation.subject import ManSubject
20 20
 from opencombat.simulation.subject import TankSubject
21 21
 from opencombat.simulation.base import TileStrategySimulation
22 22
 from opencombat.simulation.base import TileStrategySubjects
23
+from opencombat.state import StateLoaderBuilder
23 24
 from opencombat.terminal.base import CocosTerminal
24 25
 
25 26
 
26
-def main(map_dir_path: str, seed_value: int=None):
27
+def main(
28
+    map_dir_path: str,
29
+    seed_value: int=None,
30
+    state_file_path: str=None,
31
+):
27 32
     if seed_value is not None:
28 33
         seed(seed_value)
29 34
 
@@ -37,44 +42,49 @@ def main(map_dir_path: str, seed_value: int=None):
37 42
     simulation = TileStrategySimulation(config, map_file_path=map_file_path)
38 43
     subjects = TileStrategySubjects(simulation=simulation)
39 44
 
40
-    for position in ((10, 2), (11, 3), (11, 4), (12, 5),):
41
-        man = ManSubject(
42
-            config=config,
43
-            simulation=simulation,
44
-            position=position,
45
-            properties={
46
-                SELECTION_COLOR_RGB: DE_COLOR,
47
-                FLAG: FLAG_DE,
48
-                SIDE: 'AXIS',
49
-            }
50
-        )
51
-        subjects.append(man)
45
+    if state_file_path:
46
+        state_loader_builder = StateLoaderBuilder(config)
47
+        state_loader = state_loader_builder.get_state_loader(state_file_path)
48
+        subjects.extend(state_loader.get_subjects())
52 49
 
53
-    for position in ((30, 15), (31, 16), (32, 17), (33, 18),):
54
-        man = ManSubject(
55
-            config=config,
56
-            simulation=simulation,
57
-            position=position,
58
-            properties={
59
-                SELECTION_COLOR_RGB: URSS_COLOR,
60
-                FLAG: FLAG_URSS,
61
-                SIDE: 'ALLIES',
62
-            }
63
-        )
64
-        subjects.append(man)
65
-
66
-    for position in ((38, 24),):
67
-        man = TankSubject(
68
-            config=config,
69
-            simulation=simulation,
70
-            position=position,
71
-            properties={
72
-                SELECTION_COLOR_RGB: URSS_COLOR,
73
-                FLAG: FLAG_URSS,
74
-                SIDE: 'ALLIES',
75
-            }
76
-        )
77
-        subjects.append(man)
50
+    # for position in ((10, 2), (11, 3), (11, 4), (12, 5),):
51
+    #     man = ManSubject(
52
+    #         config=config,
53
+    #         simulation=simulation,
54
+    #         position=position,
55
+    #         properties={
56
+    #             SELECTION_COLOR_RGB: DE_COLOR,
57
+    #             FLAG: FLAG_DE,
58
+    #             SIDE: 'AXIS',
59
+    #         }
60
+    #     )
61
+    #     subjects.append(man)
62
+    #
63
+    # for position in ((30, 15), (31, 16), (32, 17), (33, 18),):
64
+    #     man = ManSubject(
65
+    #         config=config,
66
+    #         simulation=simulation,
67
+    #         position=position,
68
+    #         properties={
69
+    #             SELECTION_COLOR_RGB: URSS_COLOR,
70
+    #             FLAG: FLAG_URSS,
71
+    #             SIDE: 'ALLIES',
72
+    #         }
73
+    #     )
74
+    #     subjects.append(man)
75
+    #
76
+    # for position in ((38, 24),):
77
+    #     man = TankSubject(
78
+    #         config=config,
79
+    #         simulation=simulation,
80
+    #         position=position,
81
+    #         properties={
82
+    #             SELECTION_COLOR_RGB: URSS_COLOR,
83
+    #             FLAG: FLAG_URSS,
84
+    #             SIDE: 'ALLIES',
85
+    #         }
86
+    #     )
87
+    #     subjects.append(man)
78 88
 
79 89
     simulation.subjects = subjects
80 90