Browse Source

Merge branch 'feature/select_troops'

Bastien Sevajol 5 years ago
parent
commit
36f94e3246
42 changed files with 1192 additions and 89 deletions
  1. 4 2
      README.md
  2. 6 0
      config.yaml
  3. 4 0
      opencombat/exception.py
  4. 0 0
      opencombat/game/__init__.py
  5. 8 8
      opencombat/game/actor.py
  6. 0 0
      opencombat/game/animation.py
  7. 11 11
      opencombat/game/base.py
  8. 0 0
      opencombat/game/const.py
  9. 1 1
      opencombat/game/fire.py
  10. 2 2
      opencombat/game/image.py
  11. 0 0
      opencombat/game/move.py
  12. 0 0
      opencombat/game/placement.py
  13. 0 0
      opencombat/game/state.py
  14. 3 3
      opencombat/game/weapon.py
  15. 17 0
      opencombat/gui.py
  16. 11 60
      opencombat/state.py
  17. 1 0
      opencombat/strategy/__init__.py
  18. 45 0
      opencombat/strategy/manager.py
  19. 1 0
      opencombat/strategy/selection/__init__.py
  20. 207 0
      opencombat/strategy/selection/gui.py
  21. 1 0
      opencombat/strategy/team/__init__.py
  22. 34 0
      opencombat/strategy/team/model.py
  23. 114 0
      opencombat/strategy/team/stash.py
  24. 37 0
      opencombat/strategy/teams.xml
  25. 33 0
      opencombat/strategy/teams.xsd
  26. 111 0
      opencombat/strategy/troops.py
  27. 3 0
      opencombat/strategy/troops_template.xml
  28. 1 0
      opencombat/strategy/unit/__init__.py
  29. 34 0
      opencombat/strategy/unit/model.py
  30. 83 0
      opencombat/strategy/unit/stash.py
  31. 11 0
      opencombat/strategy/units.xml
  32. 21 0
      opencombat/strategy/units.xsd
  33. 1 1
      opencombat/terminal/base.py
  34. 75 0
      opencombat/xml.py
  35. 3 1
      run.py
  36. 70 0
      select_troops.py
  37. 10 0
      test_config.yaml
  38. 37 0
      tests/fixtures/teams.xml
  39. 11 0
      tests/fixtures/units.xml
  40. 0 0
      tests/strategy/__init__.py
  41. 107 0
      tests/strategy/test_teams.py
  42. 74 0
      tests/strategy/test_units.py

+ 4 - 2
README.md View File

10
 
10
 
11
 Tested only under linux, debian/Ubuntu. Before install project, install OS packages:
11
 Tested only under linux, debian/Ubuntu. Before install project, install OS packages:
12
 
12
 
13
-    build-essential libsdl1.2debian libsdl-image1.2 libsdl-image1.2-dev libsdl-ttf2.0-0 libsdl-ttf2.0-dev libsdl-mixer1.2 libsdl-mixer1.2-dev redis-server
13
+    build-essential python3-tk libsdl1.2debian libsdl-image1.2 libsdl-image1.2-dev libsdl-ttf2.0-0 libsdl-ttf2.0-dev libsdl-mixer1.2 libsdl-mixer1.2-dev redis-server
14
 
14
 
15
 Python version: 3.5+
15
 Python version: 3.5+
16
 
16
 
31
 
31
 
32
 ## Troops selection
32
 ## Troops selection
33
 
33
 
34
-Not developed yet
34
+Start troops selection GUI with:
35
+
36
+    python select_troops.py --country URSS --country DE
35
 
37
 
36
 ## Troops Placement phase
38
 ## Troops Placement phase
37
 
39
 

+ 6 - 0
config.yaml View File

25
     state_dumper: "opencombat.state.StateDumper"
25
     state_dumper: "opencombat.state.StateDumper"
26
     state_schema: "opencombat/state.xsd"
26
     state_schema: "opencombat/state.xsd"
27
     state_template: "opencombat/state_template.xml"
27
     state_template: "opencombat/state_template.xml"
28
+    unit_stash: "opencombat.strategy.unit.stash.UnitStash"
29
+    team_stash: "opencombat.strategy.team.stash.TeamStash"
30
+    teams_schema: "opencombat/strategy/teams.xsd"
31
+    units_schema: "opencombat/strategy/units.xsd"
32
+    troop_dumper: "opencombat.strategy.troops.TroopDumper"
33
+    troop_schema: "opencombat/strategy/troops.xsd"
28
     cache_dir_path: 'cache'
34
     cache_dir_path: 'cache'
29
     include_path:
35
     include_path:
30
       maps:
36
       maps:

+ 4 - 0
opencombat/exception.py View File

23
 
23
 
24
 class NotFoundError(OpenCombatException):
24
 class NotFoundError(OpenCombatException):
25
     pass
25
     pass
26
+
27
+
28
+class NotFoundException(OpenCombatException):
29
+    pass

opencombat/gui/__init__.py → opencombat/game/__init__.py View File


opencombat/gui/actor.py → opencombat/game/actor.py View File

14
 from opencombat.exception import UnknownWeapon
14
 from opencombat.exception import UnknownWeapon
15
 from opencombat.exception import WrongMode
15
 from opencombat.exception import WrongMode
16
 from opencombat.exception import UnknownFiringAnimation
16
 from opencombat.exception import UnknownFiringAnimation
17
-from opencombat.gui.animation import ANIMATION_CRAWL
18
-from opencombat.gui.animation import ANIMATION_WALK
19
-from opencombat.gui.const import MODE_MAN_STAND_UP
20
-from opencombat.gui.const import MODE_MAN_CRAWLING
21
-from opencombat.gui.image import TileImageCacheManager
22
-from opencombat.gui.weapon import RIFFLE
23
-from opencombat.gui.weapon import WeaponImageApplier
17
+from opencombat.game.animation import ANIMATION_CRAWL
18
+from opencombat.game.animation import ANIMATION_WALK
19
+from opencombat.game.const import MODE_MAN_STAND_UP
20
+from opencombat.game.const import MODE_MAN_CRAWLING
21
+from opencombat.game.image import TileImageCacheManager
22
+from opencombat.game.weapon import RIFFLE
23
+from opencombat.game.weapon import WeaponImageApplier
24
 from opencombat.user_action import UserAction
24
 from opencombat.user_action import UserAction
25
 
25
 
26
 if typing.TYPE_CHECKING:
26
 if typing.TYPE_CHECKING:
27
-    from opencombat.gui.fire import GuiFiringEvent
27
+    from opencombat.game.fire import GuiFiringEvent
28
 
28
 
29
 
29
 
30
 MODE_DEFAULT = 'MODE_DEFAULT'
30
 MODE_DEFAULT = 'MODE_DEFAULT'

opencombat/gui/animation.py → opencombat/game/animation.py View File


opencombat/gui/base.py → opencombat/game/base.py View File

18
 from synergine2_cocos2d.middleware import MapMiddleware
18
 from synergine2_cocos2d.middleware import MapMiddleware
19
 from synergine2_cocos2d.util import PathManager
19
 from synergine2_cocos2d.util import PathManager
20
 
20
 
21
-from opencombat.gui.animation import ANIMATION_WALK
22
-from opencombat.gui.animation import ANIMATION_CRAWL
23
-from opencombat.gui.fire import GuiFiringEvent
24
-from opencombat.gui.placement import SetSubjectPositionsInteraction
25
-from opencombat.gui.state import SaveStateInteraction
21
+from opencombat.game.animation import ANIMATION_WALK
22
+from opencombat.game.animation import ANIMATION_CRAWL
23
+from opencombat.game.fire import GuiFiringEvent
24
+from opencombat.game.placement import SetSubjectPositionsInteraction
25
+from opencombat.game.state import SaveStateInteraction
26
 from opencombat.simulation.interior import InteriorManager
26
 from opencombat.simulation.interior import InteriorManager
27
 from opencombat.simulation.tmx import TileMap
27
 from opencombat.simulation.tmx import TileMap
28
 from opencombat.user_action import UserAction
28
 from opencombat.user_action import UserAction
45
 from opencombat.simulation.event import DieEvent
45
 from opencombat.simulation.event import DieEvent
46
 from opencombat.simulation.subject import ManSubject
46
 from opencombat.simulation.subject import ManSubject
47
 from opencombat.simulation.subject import TankSubject
47
 from opencombat.simulation.subject import TankSubject
48
-from opencombat.gui.actor import Man as ManActor
49
-from opencombat.gui.actor import HeavyVehicle as HeavyVehicleActor
48
+from opencombat.game.actor import Man as ManActor
49
+from opencombat.game.actor import HeavyVehicle as HeavyVehicleActor
50
 
50
 
51
 
51
 
52
 class EditLayer(BaseEditLayer):
52
 class EditLayer(BaseEditLayer):
290
         )
290
         )
291
 
291
 
292
     def before_run(self) -> None:
292
     def before_run(self) -> None:
293
-        from opencombat.gui.move import MoveActorInteraction
294
-        from opencombat.gui.move import MoveFastActorInteraction
295
-        from opencombat.gui.move import MoveCrawlActorInteraction
296
-        from opencombat.gui.fire import FireActorInteraction
293
+        from opencombat.game.move import MoveActorInteraction
294
+        from opencombat.game.move import MoveFastActorInteraction
295
+        from opencombat.game.move import MoveCrawlActorInteraction
296
+        from opencombat.game.fire import FireActorInteraction
297
 
297
 
298
         self.layer_manager.interaction_manager.register(
298
         self.layer_manager.interaction_manager.register(
299
             MoveActorInteraction,
299
             MoveActorInteraction,

opencombat/gui/const.py → opencombat/game/const.py View File


opencombat/gui/fire.py → opencombat/game/fire.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import typing
2
 import typing
3
 
3
 
4
-from opencombat.gui.actor import BaseActor
4
+from opencombat.game.actor import BaseActor
5
 from opencombat.simulation.event import DEFAULT_WEAPON_TYPE
5
 from opencombat.simulation.event import DEFAULT_WEAPON_TYPE
6
 from opencombat.simulation.fire import RequestFireBehaviour
6
 from opencombat.simulation.fire import RequestFireBehaviour
7
 from synergine2_cocos2d.interaction import BaseActorInteraction
7
 from synergine2_cocos2d.interaction import BaseActorInteraction

opencombat/gui/image.py → opencombat/game/image.py View File

12
 from opencombat.exception import UnknownFiringAnimation
12
 from opencombat.exception import UnknownFiringAnimation
13
 
13
 
14
 if typing.TYPE_CHECKING:
14
 if typing.TYPE_CHECKING:
15
-    from opencombat.gui.actor import BaseActor
15
+    from opencombat.game.actor import BaseActor
16
 
16
 
17
 
17
 
18
 class FiringImageCache(ImageCache):
18
 class FiringImageCache(ImageCache):
72
     ) -> None:
72
     ) -> None:
73
         super().__init__(actor, config)
73
         super().__init__(actor, config)
74
         self.firing_cache = FiringImageCache()
74
         self.firing_cache = FiringImageCache()
75
-        from opencombat.gui.actor import BaseActor
75
+        from opencombat.game.actor import BaseActor
76
         self.actor = typing.cast(BaseActor, self.actor)
76
         self.actor = typing.cast(BaseActor, self.actor)
77
         self.path_manager = PathManager(
77
         self.path_manager = PathManager(
78
             self.config.resolve('global.include_path.graphics'),
78
             self.config.resolve('global.include_path.graphics'),

opencombat/gui/move.py → opencombat/game/move.py View File


opencombat/gui/placement.py → opencombat/game/placement.py View File


opencombat/gui/state.py → opencombat/game/state.py View File


opencombat/gui/weapon.py → opencombat/game/weapon.py View File

8
 from synergine2_cocos2d.util import PathManager
8
 from synergine2_cocos2d.util import PathManager
9
 
9
 
10
 from opencombat.exception import UnknownWeapon
10
 from opencombat.exception import UnknownWeapon
11
-from opencombat.gui.const import MODE_MAN_STAND_UP
12
-from opencombat.gui.const import MODE_MAN_CRAWLING
11
+from opencombat.game.const import MODE_MAN_STAND_UP
12
+from opencombat.game.const import MODE_MAN_CRAWLING
13
 
13
 
14
 if typing.TYPE_CHECKING:
14
 if typing.TYPE_CHECKING:
15
-    from opencombat.gui.actor import BaseActor
15
+    from opencombat.game.actor import BaseActor
16
 
16
 
17
 RIFFLE = 'RIFFLE'
17
 RIFFLE = 'RIFFLE'
18
 
18
 

+ 17 - 0
opencombat/gui.py View File

1
+# coding: utf-8
2
+from logging import Logger
3
+from tkinter import Tk
4
+
5
+from synergine2.config import Config
6
+from synergine2.log import get_logger
7
+
8
+
9
+class Gui(object):
10
+    def __init__(
11
+        self,
12
+        config: Config,
13
+        master: Tk,
14
+    ) -> None:
15
+        self._config = config
16
+        self._logger = get_logger(self.__class__.__name__, config)
17
+        self._master = master

+ 11 - 60
opencombat/state.py View File

7
 from synergine2.config import Config
7
 from synergine2.config import Config
8
 from synergine2.log import get_logger
8
 from synergine2.log import get_logger
9
 
9
 
10
-from opencombat.exception import StateLoadError
11
 from opencombat.exception import NotFoundError
10
 from opencombat.exception import NotFoundError
12
 from opencombat.simulation.base import TileStrategySimulation
11
 from opencombat.simulation.base import TileStrategySimulation
13
 from opencombat.simulation.subject import TileSubject
12
 from opencombat.simulation.subject import TileSubject
14
 from opencombat.util import get_class_from_string_path
13
 from opencombat.util import get_class_from_string_path
15
 from opencombat.util import pretty_xml
14
 from opencombat.util import pretty_xml
16
 from opencombat.util import get_text_xml_element
15
 from opencombat.util import get_text_xml_element
16
+from opencombat.xml import XmlValidator
17
 
17
 
18
 
18
 
19
 class State(object):
19
 class State(object):
195
         self._config = config
195
         self._config = config
196
         self._simulation = simulation
196
         self._simulation = simulation
197
 
197
 
198
+        schema_file_path = self._config.get(
199
+            'global.state_schema',
200
+            'opencombat/state.xsd',
201
+        )
202
+        self._xml_validator = XmlValidator(
203
+            config,
204
+            schema_file_path,
205
+        )
206
+
198
     def get_state(
207
     def get_state(
199
         self,
208
         self,
200
         state_file_path: str,
209
         state_file_path: str,
209
         self,
218
         self,
210
         state_file_path: str,
219
         state_file_path: str,
211
     ) -> Element:
220
     ) -> Element:
212
-        # open and read schema file
213
-        schema_file_path = self._config.get(
214
-            'global.state_schema',
215
-            'opencombat/state.xsd',
216
-        )
217
-        with open(schema_file_path, 'r') as schema_file:
218
-            schema_to_check = schema_file.read()
219
-
220
-        # open and read xml file
221
-        with open(state_file_path, 'r') as xml_file:
222
-            xml_to_check = xml_file.read()
223
-
224
-        xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
225
-        xmlschema = etree.XMLSchema(xmlschema_doc)
226
-
227
-        try:
228
-            doc = etree.fromstring(xml_to_check.encode('utf-8'))
229
-        # check for file IO error
230
-        except IOError as exc:
231
-            self._logger.error(exc)
232
-            raise StateLoadError('Invalid File "{}": {}'.format(
233
-                state_file_path,
234
-                str(exc),
235
-            ))
236
-        # check for XML syntax errors
237
-        except etree.XMLSyntaxError as exc:
238
-            self._logger.error(exc)
239
-            raise StateLoadError('XML Syntax Error in "{}": {}'.format(
240
-                state_file_path,
241
-                str(exc.error_log),
242
-            ))
243
-        except Exception as exc:
244
-            self._logger.error(exc)
245
-            raise StateLoadError('Unknown error with "{}": {}'.format(
246
-                state_file_path,
247
-                str(exc),
248
-            ))
249
-
250
-        # validate against schema
251
-        try:
252
-            xmlschema.assertValid(doc)
253
-        except etree.DocumentInvalid as exc:
254
-            self._logger.error(exc)
255
-            raise StateLoadError(
256
-                'Schema validation error with "{}": {}'.format(
257
-                    state_file_path,
258
-                    str(exc),
259
-                )
260
-            )
261
-        except Exception as exc:
262
-            self._logger.error(exc)
263
-            raise StateLoadError(
264
-                'Unknown validation error with "{}": {}'.format(
265
-                    state_file_path,
266
-                    str(exc),
267
-                )
268
-            )
269
-
270
-        return doc
221
+        return self._xml_validator.validate_and_return(state_file_path)
271
 
222
 
272
 
223
 
273
 class StateConstructorBuilder(object):
224
 class StateConstructorBuilder(object):

+ 1 - 0
opencombat/strategy/__init__.py View File

1
+# coding: utf-8

+ 45 - 0
opencombat/strategy/manager.py View File

1
+# coding: utf-8
2
+import typing
3
+
4
+from synergine2.config import Config
5
+from synergine2.log import get_logger
6
+
7
+from opencombat.strategy.team.model import TeamModel
8
+from opencombat.strategy.team.stash import TeamStash
9
+from opencombat.strategy.troops import TroopClassBuilder
10
+from opencombat.strategy.unit.stash import UnitStash
11
+
12
+
13
+class TroopManager(object):
14
+    def __init__(
15
+        self,
16
+        config: Config,
17
+        units_file_path: str,
18
+        teams_file_path: str,
19
+    ) -> None:
20
+        self._config = config
21
+        self._logger = get_logger('TroopManager', config)
22
+
23
+        self._builder = TroopClassBuilder(config)
24
+        self._unit_stash = self._builder.get_unit_stash(
25
+            units_file_path,
26
+        )
27
+        self._team_stash = self._builder.get_team_stash(
28
+            units_file_path,
29
+            teams_file_path,
30
+        )
31
+
32
+    @property
33
+    def team_stash(self) -> TeamStash:
34
+        return self._team_stash
35
+
36
+    @property
37
+    def unit_stash(self) -> UnitStash:
38
+        return self._unit_stash
39
+
40
+    def get_troop_dump(
41
+        self,
42
+        countries_troops: typing.Dict[str, typing.List[TeamModel]],
43
+    ):
44
+        dumper = self._builder.get_troop_dumper()
45
+        return dumper.get_troop_dump(countries_troops)

+ 1 - 0
opencombat/strategy/selection/__init__.py View File

1
+# coding: utf-8

+ 207 - 0
opencombat/strategy/selection/gui.py View File

1
+# coding: utf-8
2
+import os
3
+import typing
4
+from tkinter import Tk
5
+from tkinter import Button
6
+from tkinter import YES
7
+from tkinter import StringVar
8
+from tkinter import OptionMenu
9
+from tkinter import W
10
+from tkinter import E
11
+from tkinter import messagebox
12
+from tkinter.ttk import Combobox
13
+from tkinter.ttk import Treeview
14
+
15
+
16
+import time
17
+from synergine2.config import Config
18
+
19
+from opencombat.gui import Gui
20
+from opencombat.strategy.manager import TroopManager
21
+from opencombat.strategy.team.stash import TeamStash
22
+
23
+
24
+class SelectTroopsGui(Gui):
25
+    def __init__(
26
+        self,
27
+        config: Config,
28
+        master: Tk,
29
+        team_stash: TeamStash,
30
+        troop_manager: TroopManager,
31
+        countries: typing.List[str],
32
+        troops_dir_path: str = '.',
33
+    ) -> None:
34
+        super().__init__(config, master)
35
+        self._master.title('Troops selection')
36
+        self._countries = countries
37
+        self._team_stash = team_stash
38
+        self._troop_manager = troop_manager
39
+        self._troops_dir_path = troops_dir_path
40
+        self._countries_troops = {}  # type: typing.Dict[str, typing.List[TeamModel]]  # nopep8
41
+
42
+        # Widgets
43
+        self._selected_country_var = StringVar(self._master)
44
+        self._selected_country_var.set(countries[0])
45
+        self._selected_country_var.trace('w', self._country_changed)
46
+        self._select_country_menu = OptionMenu(
47
+            self._master,
48
+            self._selected_country_var,
49
+            *countries,
50
+        )
51
+
52
+        self._teams_var = StringVar(self._master)
53
+        self._teams_list = Combobox(
54
+            self._master,
55
+            height=10,
56
+            state='readonly',
57
+            textvariable=self._teams_var,
58
+        )
59
+
60
+        self._add_troop_var = StringVar(self._master)
61
+        self._add_troop_button = Button(
62
+            self._master,
63
+            textvariable=self._add_troop_var,
64
+            command=self._add_troop,
65
+        )
66
+        self._add_troop_var.set('Add troop')
67
+
68
+        self._remove_troop_var = StringVar(self._master)
69
+        self._remove_troop_button = Button(
70
+            self._master,
71
+            textvariable=self._remove_troop_var,
72
+            command=self._remove_troop,
73
+        )
74
+        self._remove_troop_var.set('Remove troop')
75
+
76
+        self._troops_view = Treeview(
77
+            self._master,
78
+            columns=('Soldiers',),
79
+            height=10,
80
+        )
81
+        self._troops_view.heading('#0', text='Team')
82
+        self._troops_view.heading('#1', text='Soldiers')
83
+        self._troops_view.column('#0', stretch=YES)
84
+        self._troops_view.column('#1', stretch=YES)
85
+
86
+        self._generate_troops_var = StringVar(self._master)
87
+        self._generate_troops_button = Button(
88
+            self._master,
89
+            textvariable=self._generate_troops_var,
90
+            command=self._generate_troops,
91
+        )
92
+        self._generate_troops_var.set('Generate troops')
93
+
94
+        # Layout
95
+        self._select_country_menu.grid(row=0, column=0, sticky=W)
96
+        self._teams_list.grid(row=1, column=0, sticky=W)
97
+        self._add_troop_button.grid(row=2, column=0, sticky=W)
98
+        self._troops_view.grid(row=3, column=0, sticky=W)
99
+        self._remove_troop_button.grid(row=4, column=0, sticky=W)
100
+        self._generate_troops_button.grid(row=4, column=0, sticky=E)
101
+
102
+        # Default behaviours
103
+        self._selected_country_var.set(countries[0])
104
+        self._country_changed()
105
+
106
+    def _country_changed(self, *args, **kwargs) -> None:
107
+        country = self._selected_country_var.get()
108
+
109
+        self._logger.info('Change country to "{}"'.format(
110
+            country,
111
+        ))
112
+        country_team_names = [
113
+            t.name for
114
+            t in self._team_stash.get_team_by_country(
115
+                self._selected_country_var.get(),
116
+            )
117
+        ]
118
+
119
+        self._logger.debug('Change teams for: "{}"'.format(country_team_names))
120
+        self._teams_list['values'] = country_team_names
121
+        self._teams_var.set(country_team_names[0])
122
+        self._update_troops_view(country)
123
+
124
+    def _add_troop(self, *args, **kwargs) -> None:
125
+        if self._teams_var.get():
126
+            country = self._selected_country_var.get()
127
+            team_name = self._teams_var.get()
128
+
129
+            self._logger.info('Add troop "{}" to country "{}" troops'.format(
130
+                team_name,
131
+                team_name,
132
+            ))
133
+
134
+            team_model = self._team_stash.get_team_by_name(
135
+                team_name=team_name,
136
+                team_country=country,
137
+            )
138
+            self._countries_troops.setdefault(country, []).append(
139
+                team_model,
140
+            )
141
+            self._update_troops_view(country)
142
+
143
+    def _remove_troop(self, *args, **kwargs) -> None:
144
+        selecteds = self._troops_view.selection()
145
+
146
+        for selected in selecteds:
147
+            team_name = self._troops_view.item(selected)['text']
148
+            country = self._selected_country_var.get()
149
+
150
+            self._logger.info('Remove team "{}" from country "{}"'.format(
151
+                team_name,
152
+                country,
153
+            ))
154
+
155
+            team_model = self._team_stash.get_team_by_name(
156
+                team_name=team_name,
157
+                team_country=country,
158
+            )
159
+
160
+            self._countries_troops[country].remove(team_model)
161
+
162
+        if selecteds:
163
+            self._update_troops_view(country)
164
+
165
+    def _update_troops_view(self, country: str) -> None:
166
+        teams = self._countries_troops.get(country, [])
167
+
168
+        self._troops_view.delete(*self._troops_view.get_children())
169
+        for team in teams:
170
+            self._troops_view.insert(
171
+                '',
172
+                'end',
173
+                text=team.name,
174
+                values=('o' * len(team.units,))
175
+            )
176
+
177
+    def _generate_troops(self, *args, **kwargs) -> None:
178
+        # Must have team(s) in all countries
179
+        if len(self._countries_troops.keys()) == len(self._countries) \
180
+                and all(self._countries_troops.values()):
181
+
182
+            troops_file_path = os.path.join(
183
+                self._troops_dir_path,
184
+                'troops_{}.xml'.format(str(time.time())),
185
+            )
186
+
187
+            self._logger.info('Generate troops into file "{}"'.format(
188
+                troops_file_path,
189
+            ))
190
+
191
+            troops_xml = self._troop_manager.get_troop_dump(
192
+                self._countries_troops,
193
+            )
194
+            with open(troops_file_path, 'w+') as file:
195
+                file.write(troops_xml)
196
+
197
+            messagebox.showinfo(
198
+                'Troops file generated',
199
+                'Troops file successfully generate at "{}"'.format(
200
+                    troops_file_path,
201
+                )
202
+            )
203
+        else:
204
+            messagebox.showinfo(
205
+                'Missing information',
206
+                'All countries must have teams',
207
+            )

+ 1 - 0
opencombat/strategy/team/__init__.py View File

1
+# coding: utf-8

+ 34 - 0
opencombat/strategy/team/model.py View File

1
+# coding: utf-8
2
+import typing
3
+
4
+from opencombat.strategy.unit.model import UnitModel
5
+
6
+
7
+class TeamModel(object):
8
+    def __init__(
9
+        self,
10
+        id_: str,
11
+        name: str,
12
+        country: str,
13
+        units: typing.List[UnitModel],
14
+    ) -> None:
15
+        self._id = id_
16
+        self._name = name
17
+        self._country = country
18
+        self._units = units
19
+
20
+    @property
21
+    def id(self) -> str:
22
+        return self._id
23
+
24
+    @property
25
+    def name(self) -> str:
26
+        return self._name
27
+
28
+    @property
29
+    def country(self) -> str:
30
+        return self._country
31
+
32
+    @property
33
+    def units(self) -> typing.List[UnitModel]:
34
+        return self._units

+ 114 - 0
opencombat/strategy/team/stash.py View File

1
+# coding: utf-8
2
+import typing
3
+from _elementtree import Element
4
+
5
+from synergine2.config import Config
6
+
7
+from opencombat.exception import NotFoundException
8
+from opencombat.strategy.team.model import TeamModel
9
+from opencombat.strategy.unit.stash import UnitStash
10
+from opencombat.util import get_text_xml_element
11
+from opencombat.xml import XmlValidator
12
+
13
+
14
+class TeamStash(object):
15
+    def __init__(
16
+        self,
17
+        config: Config,
18
+        teams_file_path: str,
19
+        unit_stash: UnitStash,
20
+    ) -> None:
21
+        self._config = config
22
+        self._teams = None  # type: typing.List[TeamModel]
23
+        self._unit_stash = unit_stash
24
+        self._teams_file_path = teams_file_path
25
+
26
+        self.schema_file_path = self._config.get(
27
+            'global.teams_schema',
28
+            'opencombat/strategy/teams.xsd',
29
+        )
30
+        self._xml_validator = XmlValidator(
31
+            config,
32
+            self.schema_file_path,
33
+        )
34
+        self._root_element = self._xml_validator.validate_and_return(
35
+            self._teams_file_path,
36
+        )
37
+
38
+    def _get_computed_teams(self) -> typing.List[TeamModel]:
39
+        teams = []
40
+
41
+        for team_element in self._root_element.findall('team'):
42
+            team_element = typing.cast(Element, team_element)
43
+
44
+            team_id = team_element.attrib['id']
45
+            team_country = team_element.attrib['country']
46
+            team_name = get_text_xml_element(team_element, 'name')
47
+            team_units = []
48
+
49
+            units_element = team_element.find('units')
50
+            for unit_element in units_element.findall('unit'):
51
+                unit_id = get_text_xml_element(unit_element, 'id')
52
+                unit = self._unit_stash.get_unit(unit_id, team_country)
53
+                team_units.append(unit)
54
+
55
+            teams.append(
56
+                TeamModel(
57
+                    id_=team_id,
58
+                    country=team_country,
59
+                    name=team_name,
60
+                    units=team_units
61
+                )
62
+            )
63
+
64
+        return teams
65
+
66
+    @property
67
+    def teams(self) -> typing.List[TeamModel]:
68
+        if self._teams is None:
69
+            self._teams = self._get_computed_teams()
70
+
71
+        return self._teams
72
+
73
+    def get_team(
74
+        self,
75
+        team_id: str,
76
+        team_country: str,
77
+    ) -> TeamModel:
78
+        for team in self.teams:
79
+            if team.id == team_id and team.country == team_country:
80
+                return team
81
+
82
+        raise NotFoundException(
83
+            'No team matching with id "{}" and country "{}" in "{}"'.format(
84
+                team_id,
85
+                team_country,
86
+                self.schema_file_path,
87
+            )
88
+        )
89
+
90
+    def get_team_by_name(
91
+        self,
92
+        team_name: str,
93
+        team_country: str,
94
+    ) -> TeamModel:
95
+        for team in self.teams:
96
+            if team.name == team_name and team.country == team_country:
97
+                return team
98
+
99
+        raise NotFoundException(
100
+            'No team matching with name "{}" and country "{}" in "{}"'.format(
101
+                team_name,
102
+                team_country,
103
+                self.schema_file_path,
104
+            )
105
+        )
106
+
107
+    def get_team_by_country(self, country: str) -> typing.List[TeamModel]:
108
+        teams = []  # type: typing.List[TeamModel]
109
+
110
+        for team in self.teams:
111
+            if team.country == country:
112
+                teams.append(team)
113
+
114
+        return teams

+ 37 - 0
opencombat/strategy/teams.xml View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<teams>
3
+    <team id="std_team" country="URSS">
4
+        <name>Standard URSS team</name>
5
+        <units>
6
+            <unit>
7
+                <id>std_soldier</id>
8
+            </unit>
9
+            <unit>
10
+                <id>std_soldier</id>
11
+            </unit>
12
+            <unit>
13
+                <id>std_soldier</id>
14
+            </unit>
15
+            <unit>
16
+                <id>std_soldier</id>
17
+            </unit>
18
+        </units>
19
+    </team>
20
+    <team id="std_team" country="DE">
21
+        <name>Standard DE team</name>
22
+        <units>
23
+            <unit>
24
+                <id>std_soldier</id>
25
+            </unit>
26
+            <unit>
27
+                <id>std_soldier</id>
28
+            </unit>
29
+            <unit>
30
+                <id>std_soldier</id>
31
+            </unit>
32
+            <unit>
33
+                <id>std_soldier</id>
34
+            </unit>
35
+        </units>
36
+    </team>
37
+</teams>

+ 33 - 0
opencombat/strategy/teams.xsd View File

1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+
4
+    <xs:element name="teams" type="teamstype"/>
5
+
6
+    <xs:complexType name="teamstype">
7
+        <xs:sequence>
8
+            <xs:element name="team" type="teamtype" maxOccurs="unbounded"/>
9
+        </xs:sequence>
10
+    </xs:complexType>
11
+
12
+    <xs:complexType name="teamtype">
13
+        <xs:sequence>
14
+            <xs:element name="name" type="xs:string" maxOccurs="1"/>
15
+            <xs:element name="units" type="unitstype" maxOccurs="1"/>
16
+        </xs:sequence>
17
+        <xs:attribute name="id" type="xs:string" use="required"/>
18
+        <xs:attribute name="country" type="xs:string" use="required"/>
19
+    </xs:complexType>
20
+
21
+    <xs:complexType name="unitstype">
22
+        <xs:sequence>
23
+            <xs:element name="unit" type="unittype" maxOccurs="unbounded" minOccurs="1"/>
24
+        </xs:sequence>
25
+    </xs:complexType>
26
+
27
+    <xs:complexType name="unittype">
28
+        <xs:sequence>
29
+            <xs:element name="id" type="xs:string" maxOccurs="1" minOccurs="1"/>
30
+        </xs:sequence>
31
+    </xs:complexType>
32
+
33
+</xs:schema>

+ 111 - 0
opencombat/strategy/troops.py View File

1
+# coding: utf-8
2
+import typing
3
+
4
+from lxml import etree
5
+
6
+from synergine2.config import Config
7
+from synergine2.log import get_logger
8
+
9
+from opencombat.strategy.team.model import TeamModel
10
+from opencombat.strategy.team.stash import TeamStash
11
+from opencombat.strategy.unit.stash import UnitStash
12
+from opencombat.util import get_class_from_string_path, pretty_xml
13
+
14
+
15
+class TroopDumper(object):
16
+    def __init__(
17
+        self,
18
+        config: Config,
19
+    ) -> None:
20
+        self._config = config
21
+        self._logger = get_logger('TroopDumper', config)
22
+
23
+    def get_troop_dump(
24
+        self,
25
+        countries_troops: typing.Dict[str, typing.List[TeamModel]],
26
+    ) -> str:
27
+        troops_template = self._config.resolve(
28
+            'global.troops_template',
29
+            'opencombat/strategy/troops_template.xml',
30
+        )
31
+        with open(troops_template, 'r') as xml_file:
32
+            template_str = xml_file.read()
33
+
34
+        parser = etree.XMLParser(remove_blank_text=True)
35
+        state_root = etree.fromstring(
36
+            template_str.encode('utf-8'),
37
+            parser,
38
+        )
39
+
40
+        for country, teams in countries_troops.items():
41
+            for team in teams:
42
+                troop_element = etree.SubElement(state_root, 'troop')
43
+                troop_element.attrib['country'] = country
44
+                troop_element.attrib['team_id'] = team.id
45
+
46
+        return pretty_xml(
47
+            etree.tostring(
48
+                state_root,
49
+            ).decode('utf-8'),
50
+        )
51
+
52
+
53
+class TroopClassBuilder(object):
54
+    def __init__(
55
+        self,
56
+        config: Config,
57
+    ) -> None:
58
+        self._logger = get_logger('TroopManagerBuilder', config)
59
+        self._config = config
60
+
61
+    def get_unit_stash(
62
+        self,
63
+        units_file_path: str,
64
+    ) -> UnitStash:
65
+        class_address = self._config.resolve(
66
+            'global.unit_stash',
67
+            'opencombat.strategy.unit.stash.UnitStash',
68
+        )
69
+        class_ = get_class_from_string_path(
70
+            self._config,
71
+            class_address,
72
+        )
73
+        return class_(
74
+            self._config,
75
+            units_file_path,
76
+        )
77
+
78
+    def get_team_stash(
79
+        self,
80
+        units_file_path: str,
81
+        teams_file_path: str,
82
+    ) -> TeamStash:
83
+        class_address = self._config.resolve(
84
+            'global.team_stash',
85
+            'opencombat.strategy.team.stash.TeamStash',
86
+        )
87
+        class_ = get_class_from_string_path(
88
+            self._config,
89
+            class_address,
90
+        )
91
+
92
+        unit_stash = self.get_unit_stash(units_file_path)
93
+        return class_(
94
+            self._config,
95
+            teams_file_path,
96
+            unit_stash=unit_stash,
97
+        )
98
+
99
+    def get_troop_dumper(self) -> TroopDumper:
100
+        class_address = self._config.resolve(
101
+            'global.troop_dumper',
102
+            'opencombat.strategy.troops.TroopDumper',
103
+        )
104
+        class_ = get_class_from_string_path(
105
+            self._config,
106
+            class_address,
107
+        )
108
+
109
+        return class_(
110
+            self._config,
111
+        )

+ 3 - 0
opencombat/strategy/troops_template.xml View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<troops>
3
+</troops>

+ 1 - 0
opencombat/strategy/unit/__init__.py View File

1
+# coding: utf-8

+ 34 - 0
opencombat/strategy/unit/model.py View File

1
+# coding: utf-8
2
+import typing
3
+
4
+from opencombat.simulation.subject import TileSubject
5
+
6
+
7
+class UnitModel(object):
8
+    def __init__(
9
+        self,
10
+        id_: str,
11
+        name: str,
12
+        class_: typing.Type[TileSubject],
13
+        country: str,
14
+    ) -> None:
15
+        self._id = id_
16
+        self._name = name
17
+        self._class = class_
18
+        self._country = country
19
+
20
+    @property
21
+    def id(self) -> str:
22
+        return self._id
23
+
24
+    @property
25
+    def name(self) -> str:
26
+        return self._name
27
+
28
+    @property
29
+    def class_(self) -> typing.Type[TileSubject]:
30
+        return self._class
31
+
32
+    @property
33
+    def country(self) -> str:
34
+        return self._country

+ 83 - 0
opencombat/strategy/unit/stash.py View File

1
+# coding: utf-8
2
+import typing
3
+from _elementtree import Element
4
+
5
+from synergine2.config import Config
6
+
7
+from opencombat.exception import NotFoundException
8
+from opencombat.strategy.team.model import UnitModel
9
+from opencombat.util import get_text_xml_element
10
+from opencombat.util import get_class_from_string_path
11
+from opencombat.xml import XmlValidator
12
+
13
+
14
+class UnitStash(object):
15
+    def __init__(
16
+        self,
17
+        config: Config,
18
+        units_file_path: str,
19
+    ) -> None:
20
+        self._config = config
21
+        self._units = None  # type: typing.List[UnitModel]
22
+
23
+        self.schema_file_path = self._config.get(
24
+            'global.teams_schema',
25
+            'opencombat/strategy/units.xsd',
26
+        )
27
+        self._xml_validator = XmlValidator(
28
+            config,
29
+            self.schema_file_path,
30
+        )
31
+        self._root_element = self._xml_validator.validate_and_return(
32
+            units_file_path,
33
+        )
34
+
35
+    def _get_computed_units(self) -> typing.List[UnitModel]:
36
+        units = []
37
+
38
+        for unit_element in self._root_element.findall('unit'):
39
+            unit_element = typing.cast(Element, unit_element)
40
+
41
+            unit_id = unit_element.attrib['id']
42
+            unit_country = unit_element.attrib['country']
43
+            unit_name = get_text_xml_element(unit_element, 'name')
44
+            unit_class_path = get_text_xml_element(unit_element, 'type')
45
+            unit_class = get_class_from_string_path(
46
+                self._config,
47
+                unit_class_path,
48
+            )
49
+
50
+            units.append(
51
+                UnitModel(
52
+                    id_=unit_id,
53
+                    name=unit_name,
54
+                    class_=unit_class,
55
+                    country=unit_country,
56
+                )
57
+            )
58
+
59
+        return units
60
+
61
+    @property
62
+    def units(self) -> typing.List[UnitModel]:
63
+        if self._units is None:
64
+            self._units = self._get_computed_units()
65
+
66
+        return self._units
67
+
68
+    def get_unit(
69
+        self,
70
+        unit_id: str,
71
+        unit_country: str,
72
+    ) -> UnitModel:
73
+        for unit in self.units:
74
+            if unit.id == unit_id and unit.country == unit_country:
75
+                return unit
76
+
77
+        raise NotFoundException(
78
+            'No unit matching with id "{}" and country "{}" in "{}"'.format(
79
+                unit_id,
80
+                unit_country,
81
+                self.schema_file_path,
82
+            )
83
+        )

+ 11 - 0
opencombat/strategy/units.xml View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<units>
3
+    <unit id="std_soldier" country="URSS">
4
+        <name>Standard soldier</name>
5
+        <type>opencombat.simulation.subject.ManSubject</type>
6
+    </unit>
7
+    <unit id="std_soldier" country="DE">
8
+        <name>Standard soldier</name>
9
+        <type>opencombat.simulation.subject.ManSubject</type>
10
+    </unit>
11
+</units>

+ 21 - 0
opencombat/strategy/units.xsd View File

1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+
4
+    <xs:element name="units" type="unitstype"/>
5
+
6
+    <xs:complexType name="unitstype">
7
+        <xs:sequence>
8
+            <xs:element name="unit" type="unittype" maxOccurs="unbounded" minOccurs="1"/>
9
+        </xs:sequence>
10
+    </xs:complexType>
11
+
12
+    <xs:complexType name="unittype">
13
+        <xs:sequence>
14
+            <xs:element name="name" type="xs:string" maxOccurs="1" minOccurs="1"/>
15
+            <xs:element name="type" type="xs:string" maxOccurs="1" minOccurs="1"/>
16
+        </xs:sequence>
17
+        <xs:attribute name="id" type="xs:string" use="required"/>
18
+        <xs:attribute name="country" type="xs:string" use="required"/>
19
+    </xs:complexType>
20
+
21
+</xs:schema>

+ 1 - 1
opencombat/terminal/base.py View File

39
         self.map_dir_path = map_dir_path
39
         self.map_dir_path = map_dir_path
40
 
40
 
41
     def run(self):
41
     def run(self):
42
-        from opencombat.gui.base import Game
42
+        from opencombat.game.base import Game
43
 
43
 
44
         self.gui = Game(
44
         self.gui = Game(
45
             self.config,
45
             self.config,

+ 75 - 0
opencombat/xml.py View File

1
+# coding: utf-8
2
+from _elementtree import Element
3
+
4
+from lxml import etree
5
+from synergine2.config import Config
6
+from synergine2.log import get_logger
7
+
8
+from opencombat.exception import StateLoadError
9
+
10
+
11
+class XmlValidator(object):
12
+    def __init__(
13
+        self,
14
+        config: Config,
15
+        schema_file_path: str,
16
+    ) -> None:
17
+        self._config = config
18
+        self._logger = get_logger('XmlValidator', config)
19
+        self._schema_file_path = schema_file_path
20
+
21
+    def validate_and_return(self, xml_file_path: str) -> Element:
22
+        with open(self._schema_file_path, 'r') as schema_file:
23
+            schema_to_check = schema_file.read()
24
+
25
+        # open and read xml file
26
+        with open(xml_file_path, 'r') as xml_file:
27
+            xml_to_check = xml_file.read()
28
+
29
+        xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
30
+        xmlschema = etree.XMLSchema(xmlschema_doc)
31
+
32
+        try:
33
+            doc = etree.fromstring(xml_to_check.encode('utf-8'))
34
+        # check for file IO error
35
+        except IOError as exc:
36
+            self._logger.error(exc)
37
+            raise StateLoadError('Invalid File "{}": {}'.format(
38
+                xml_file_path,
39
+                str(exc),
40
+            ))
41
+        # check for XML syntax errors
42
+        except etree.XMLSyntaxError as exc:
43
+            self._logger.error(exc)
44
+            raise StateLoadError('XML Syntax Error in "{}": {}'.format(
45
+                xml_file_path,
46
+                str(exc.error_log),
47
+            ))
48
+        except Exception as exc:
49
+            self._logger.error(exc)
50
+            raise StateLoadError('Unknown error in "{}": {}'.format(
51
+                xml_file_path,
52
+                str(exc),
53
+            ))
54
+
55
+        # validate against schema
56
+        try:
57
+            xmlschema.assertValid(doc)
58
+        except etree.DocumentInvalid as exc:
59
+            self._logger.error(exc)
60
+            raise StateLoadError(
61
+                'Schema validation error in "{}": {}'.format(
62
+                    xml_file_path,
63
+                    str(exc),
64
+                )
65
+            )
66
+        except Exception as exc:
67
+            self._logger.error(exc)
68
+            raise StateLoadError(
69
+                'Unknown validation error in "{}": {}'.format(
70
+                    xml_file_path,
71
+                    str(exc),
72
+                )
73
+            )
74
+
75
+        return doc

+ 3 - 1
run.py View File

71
     core.run()
71
     core.run()
72
 
72
 
73
 if __name__ == '__main__':
73
 if __name__ == '__main__':
74
-    parser = argparse.ArgumentParser(description='Run TileStrategy')
74
+    parser = argparse.ArgumentParser(
75
+        description='Run a map'
76
+    )
75
     parser.add_argument('map_dir_path', help='map directory path')
77
     parser.add_argument('map_dir_path', help='map directory path')
76
     parser.add_argument('--seed', dest='seed', default=None)
78
     parser.add_argument('--seed', dest='seed', default=None)
77
     parser.add_argument('--state', dest='state', default=None)
79
     parser.add_argument('--state', dest='state', default=None)

+ 70 - 0
select_troops.py View File

1
+# coding: utf-8
2
+import argparse
3
+import typing
4
+from tkinter import Tk
5
+
6
+from synergine2.config import Config
7
+
8
+from opencombat.strategy.manager import TroopManager
9
+from opencombat.strategy.selection.gui import SelectTroopsGui
10
+
11
+
12
+def main(
13
+    units_file_path: str,
14
+    teams_file_path: str,
15
+    countries: typing.List[str],
16
+    troops_dir_path: str = '.',
17
+) -> None:
18
+    config = Config()
19
+    config.load_yaml('config.yaml')
20
+
21
+    troop_manager = TroopManager(
22
+        config,
23
+        units_file_path=units_file_path,
24
+        teams_file_path=teams_file_path,
25
+    )
26
+
27
+    master = Tk()
28
+    gui = SelectTroopsGui(
29
+        config,
30
+        master=master,
31
+        team_stash=troop_manager.team_stash,
32
+        troop_manager=troop_manager,
33
+        countries=countries,
34
+        troops_dir_path=troops_dir_path,
35
+    )
36
+    master.mainloop()
37
+
38
+
39
+if __name__ == '__main__':
40
+    parser = argparse.ArgumentParser(
41
+        description='Display troops selection gui',
42
+    )
43
+    parser.add_argument(
44
+        '--units',
45
+        dest='units_file_path',
46
+        default='opencombat/strategy/units.xml',
47
+    )
48
+    parser.add_argument(
49
+        '--teams',
50
+        dest='teams_file_path',
51
+        default='opencombat/strategy/teams.xml',
52
+    )
53
+    parser.add_argument(
54
+        '--country',
55
+        action='append',
56
+        dest='countries',
57
+    )
58
+    parser.add_argument(
59
+        '--troops-dir-path',
60
+        dest='troops_dir_path',
61
+        default='.',
62
+    )
63
+    args = parser.parse_args()
64
+
65
+    main(
66
+        units_file_path=args.units_file_path,
67
+        teams_file_path=args.teams_file_path,
68
+        countries=args.countries,
69
+        troops_dir_path=args.troops_dir_path,
70
+    )

+ 10 - 0
test_config.yaml View File

21
       draw_interior_gap: 2
21
       draw_interior_gap: 2
22
 
22
 
23
 global:
23
 global:
24
+    state_loader: "opencombat.state.StateLoader"
25
+    state_dumper: "opencombat.state.StateDumper"
26
+    state_schema: "opencombat/state.xsd"
27
+    state_template: "opencombat/state_template.xml"
28
+    unit_stash: "opencombat.strategy.unit.stash.UnitStash"
29
+    team_stash: "opencombat.strategy.team.stash.TeamStash"
30
+    teams_schema: "opencombat/strategy/teams.xsd"
31
+    units_schema: "opencombat/strategy/units.xsd"
32
+    troop_dumper: "opencombat.strategy.troops.TroopDumper"
33
+    troop_schema: "opencombat/strategy/troops.xsd"
24
     cache_dir_path: 'cache'
34
     cache_dir_path: 'cache'
25
     include_path:
35
     include_path:
26
       maps:
36
       maps:

+ 37 - 0
tests/fixtures/teams.xml View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<teams>
3
+    <team id="std_team" country="URSS">
4
+        <name>Standard URSS team</name>
5
+        <units>
6
+            <unit>
7
+                <id>std_soldier</id>
8
+            </unit>
9
+            <unit>
10
+                <id>std_soldier</id>
11
+            </unit>
12
+            <unit>
13
+                <id>std_soldier</id>
14
+            </unit>
15
+            <unit>
16
+                <id>std_soldier</id>
17
+            </unit>
18
+        </units>
19
+    </team>
20
+    <team id="std_team" country="DE">
21
+        <name>Standard DE team</name>
22
+        <units>
23
+            <unit>
24
+                <id>std_soldier</id>
25
+            </unit>
26
+            <unit>
27
+                <id>std_soldier</id>
28
+            </unit>
29
+            <unit>
30
+                <id>std_soldier</id>
31
+            </unit>
32
+            <unit>
33
+                <id>std_soldier</id>
34
+            </unit>
35
+        </units>
36
+    </team>
37
+</teams>

+ 11 - 0
tests/fixtures/units.xml View File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<units>
3
+    <unit id="std_soldier" country="URSS">
4
+        <name>Standard soldier</name>
5
+        <type>opencombat.simulation.subject.ManSubject</type>
6
+    </unit>
7
+    <unit id="std_soldier" country="DE">
8
+        <name>Standard soldier</name>
9
+        <type>opencombat.simulation.subject.ManSubject</type>
10
+    </unit>
11
+</units>

+ 0 - 0
tests/strategy/__init__.py View File


+ 107 - 0
tests/strategy/test_teams.py View File

1
+# coding: utf-8
2
+import pytest
3
+from synergine2.config import Config
4
+
5
+from opencombat.exception import NotFoundException
6
+from opencombat.strategy.team.model import TeamModel
7
+from opencombat.strategy.team.stash import TeamStash
8
+from opencombat.strategy.unit.model import UnitModel
9
+from opencombat.strategy.unit.stash import UnitStash
10
+
11
+
12
+@pytest.fixture
13
+def unit_stash(
14
+    config: Config,
15
+) -> UnitStash:
16
+    return UnitStash(
17
+        config,
18
+        'tests/fixtures/units.xml',
19
+    )
20
+
21
+
22
+def test_units_stash__ok__instantiate(
23
+    config: Config,
24
+    unit_stash: UnitStash,
25
+):
26
+    TeamStash(
27
+        config,
28
+        'opencombat/strategy/teams.xml',
29
+        unit_stash=unit_stash,
30
+    )
31
+
32
+
33
+def test_team_stash__ok__get_teams(
34
+    config: Config,
35
+    unit_stash: UnitStash,
36
+):
37
+    stash = TeamStash(
38
+        config,
39
+        'tests/fixtures/teams.xml',
40
+        unit_stash=unit_stash,
41
+    )
42
+    assert stash.teams
43
+    assert 2 == len(stash.teams)
44
+    assert isinstance(stash.teams[0], TeamModel)
45
+    assert isinstance(stash.teams[1], TeamModel)
46
+
47
+    assert 'std_team' == stash.teams[0].id
48
+    assert 'Standard team' == stash.teams[0].name
49
+    assert 'URSS' == stash.teams[0].country
50
+    assert stash.teams[0].units
51
+    assert 4 == len(stash.teams[0].units)
52
+    assert isinstance(stash.teams[0].units[0], UnitModel)
53
+    assert 'std_soldier' == stash.teams[0].units[0].id
54
+    assert 'std_soldier' == stash.teams[0].units[1].id
55
+    assert 'std_soldier' == stash.teams[0].units[2].id
56
+    assert 'std_soldier' == stash.teams[0].units[3].id
57
+
58
+    assert 'std_team' == stash.teams[1].id
59
+    assert 'Standard team' == stash.teams[1].name
60
+    assert 'DE' == stash.teams[1].country
61
+    assert stash.teams[0].units
62
+    assert 4 == len(stash.teams[1].units)
63
+    assert isinstance(stash.teams[1].units[0], UnitModel)
64
+    assert 'std_soldier' == stash.teams[1].units[0].id
65
+    assert 'std_soldier' == stash.teams[1].units[1].id
66
+    assert 'std_soldier' == stash.teams[1].units[2].id
67
+    assert 'std_soldier' == stash.teams[1].units[3].id
68
+
69
+
70
+def test_teams_stash__ok__get_unit(
71
+    config: Config,
72
+    unit_stash: UnitStash,
73
+):
74
+    stash = TeamStash(
75
+        config,
76
+        'tests/fixtures/teams.xml',
77
+        unit_stash=unit_stash,
78
+    )
79
+    assert stash.get_team('std_team', 'URSS')
80
+
81
+
82
+def test_teams_stash__error__get_team_wrong_country(
83
+    config: Config,
84
+    unit_stash: UnitStash,
85
+):
86
+    stash = TeamStash(
87
+        config,
88
+        'tests/fixtures/teams.xml',
89
+        unit_stash=unit_stash,
90
+    )
91
+
92
+    with pytest.raises(NotFoundException):
93
+        stash.get_team('std_team', 'UNKNOWN')
94
+
95
+
96
+def test_teams_stash__error__get_team_wrong_id(
97
+    config: Config,
98
+    unit_stash: UnitStash,
99
+):
100
+    stash = TeamStash(
101
+        config,
102
+        'tests/fixtures/teams.xml',
103
+        unit_stash=unit_stash,
104
+    )
105
+
106
+    with pytest.raises(NotFoundException):
107
+        stash.get_team('unknown', 'URSS')

+ 74 - 0
tests/strategy/test_units.py View File

1
+# coding: utf-8
2
+import pytest
3
+from synergine2.config import Config
4
+
5
+from opencombat.exception import NotFoundException
6
+from opencombat.simulation.subject import ManSubject
7
+from opencombat.strategy.team.model import UnitModel
8
+from opencombat.strategy.unit.stash import UnitStash
9
+
10
+
11
+def test_units_stash__ok__instantiate(
12
+    config: Config,
13
+):
14
+    UnitStash(
15
+        config,
16
+        'opencombat/strategy/units.xml',
17
+    )
18
+
19
+
20
+def test_units_stash__ok__get_units(
21
+    config: Config,
22
+):
23
+    stash = UnitStash(
24
+        config,
25
+        'tests/fixtures/units.xml',
26
+    )
27
+    assert stash.units
28
+    assert 2 == len(stash.units)
29
+    assert isinstance(stash.units[0], UnitModel)
30
+    assert isinstance(stash.units[1], UnitModel)
31
+
32
+    assert 'std_soldier' == stash.units[0].id
33
+    assert 'Standard soldier' == stash.units[0].name
34
+    assert 'URSS' == stash.units[0].country
35
+    assert ManSubject == stash.units[0].class_
36
+
37
+    assert 'std_soldier' == stash.units[1].id
38
+    assert 'Standard soldier' == stash.units[1].name
39
+    assert 'DE' == stash.units[1].country
40
+    assert ManSubject == stash.units[1].class_
41
+
42
+
43
+def test_units_stash__ok__get_unit(
44
+    config: Config,
45
+):
46
+    stash = UnitStash(
47
+        config,
48
+        'tests/fixtures/units.xml',
49
+    )
50
+    assert stash.get_unit('std_soldier', 'URSS')
51
+
52
+
53
+def test_units_stash__error__get_unit_wrong_country(
54
+    config: Config,
55
+):
56
+    stash = UnitStash(
57
+        config,
58
+        'tests/fixtures/units.xml',
59
+    )
60
+
61
+    with pytest.raises(NotFoundException):
62
+        stash.get_unit('std_soldier', 'UNKNOWN')
63
+
64
+
65
+def test_units_stash__error__get_unit_wrong_id(
66
+    config: Config,
67
+):
68
+    stash = UnitStash(
69
+        config,
70
+        'tests/fixtures/units.xml',
71
+    )
72
+
73
+    with pytest.raises(NotFoundException):
74
+        stash.get_unit('unknown', 'URSS')