pywargame 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. pywargame/__init__.py +2 -0
  2. pywargame/common/__init__.py +3 -0
  3. pywargame/common/collector.py +87 -0
  4. pywargame/common/dicedraw.py +363 -0
  5. pywargame/common/drawdice.py +40 -0
  6. pywargame/common/singleton.py +22 -0
  7. pywargame/common/test.py +25 -0
  8. pywargame/common/verbose.py +59 -0
  9. pywargame/common/verboseguard.py +53 -0
  10. pywargame/cyberboard/__init__.py +18 -0
  11. pywargame/cyberboard/archive.py +283 -0
  12. pywargame/cyberboard/base.py +63 -0
  13. pywargame/cyberboard/board.py +462 -0
  14. pywargame/cyberboard/cell.py +200 -0
  15. pywargame/cyberboard/collect.py +49 -0
  16. pywargame/cyberboard/collectgbx0pwd.py +30 -0
  17. pywargame/cyberboard/collectgbxext.py +30 -0
  18. pywargame/cyberboard/collectgsnexp.py +32 -0
  19. pywargame/cyberboard/collectgsnext.py +30 -0
  20. pywargame/cyberboard/draw.py +396 -0
  21. pywargame/cyberboard/exporter.py +1132 -0
  22. pywargame/cyberboard/extractor.py +240 -0
  23. pywargame/cyberboard/features.py +17 -0
  24. pywargame/cyberboard/gamebox.py +81 -0
  25. pywargame/cyberboard/gbxexp.py +76 -0
  26. pywargame/cyberboard/gbxext.py +64 -0
  27. pywargame/cyberboard/gsnexp.py +147 -0
  28. pywargame/cyberboard/gsnext.py +59 -0
  29. pywargame/cyberboard/head.py +111 -0
  30. pywargame/cyberboard/image.py +76 -0
  31. pywargame/cyberboard/main.py +47 -0
  32. pywargame/cyberboard/mark.py +102 -0
  33. pywargame/cyberboard/palette.py +36 -0
  34. pywargame/cyberboard/piece.py +169 -0
  35. pywargame/cyberboard/player.py +36 -0
  36. pywargame/cyberboard/scenario.py +115 -0
  37. pywargame/cyberboard/testgrid.py +156 -0
  38. pywargame/cyberboard/tile.py +121 -0
  39. pywargame/cyberboard/tray.py +68 -0
  40. pywargame/cyberboard/windows.py +41 -0
  41. pywargame/cyberboard/zeropwd.py +45 -0
  42. pywargame/cyberboard.py +2728 -0
  43. pywargame/gbx0pwd.py +2776 -0
  44. pywargame/gbxextract.py +2795 -0
  45. pywargame/gsnexport.py +16499 -0
  46. pywargame/gsnextract.py +2790 -0
  47. pywargame/latex/__init__.py +2 -0
  48. pywargame/latex/collect.py +34 -0
  49. pywargame/latex/latexexporter.py +4010 -0
  50. pywargame/latex/main.py +184 -0
  51. pywargame/vassal/__init__.py +66 -0
  52. pywargame/vassal/base.py +139 -0
  53. pywargame/vassal/board.py +243 -0
  54. pywargame/vassal/buildfile.py +60 -0
  55. pywargame/vassal/chart.py +79 -0
  56. pywargame/vassal/chessclock.py +197 -0
  57. pywargame/vassal/collect.py +98 -0
  58. pywargame/vassal/collectpatch.py +28 -0
  59. pywargame/vassal/command.py +21 -0
  60. pywargame/vassal/documentation.py +322 -0
  61. pywargame/vassal/dumpcollect.py +28 -0
  62. pywargame/vassal/dumpvsav.py +28 -0
  63. pywargame/vassal/element.py +439 -0
  64. pywargame/vassal/exporter.py +89 -0
  65. pywargame/vassal/extension.py +101 -0
  66. pywargame/vassal/folder.py +103 -0
  67. pywargame/vassal/game.py +940 -0
  68. pywargame/vassal/gameelements.py +1091 -0
  69. pywargame/vassal/globalkey.py +127 -0
  70. pywargame/vassal/globalproperty.py +433 -0
  71. pywargame/vassal/grid.py +573 -0
  72. pywargame/vassal/map.py +1061 -0
  73. pywargame/vassal/mapelements.py +1020 -0
  74. pywargame/vassal/merge.py +57 -0
  75. pywargame/vassal/merger.py +460 -0
  76. pywargame/vassal/moduledata.py +275 -0
  77. pywargame/vassal/mrgcollect.py +31 -0
  78. pywargame/vassal/patch.py +44 -0
  79. pywargame/vassal/patchcollect.py +28 -0
  80. pywargame/vassal/player.py +83 -0
  81. pywargame/vassal/save.py +495 -0
  82. pywargame/vassal/skel.py +380 -0
  83. pywargame/vassal/trait.py +224 -0
  84. pywargame/vassal/traits/__init__.py +36 -0
  85. pywargame/vassal/traits/area.py +50 -0
  86. pywargame/vassal/traits/basic.py +35 -0
  87. pywargame/vassal/traits/calculatedproperty.py +22 -0
  88. pywargame/vassal/traits/cargo.py +29 -0
  89. pywargame/vassal/traits/click.py +41 -0
  90. pywargame/vassal/traits/clone.py +28 -0
  91. pywargame/vassal/traits/delete.py +24 -0
  92. pywargame/vassal/traits/deselect.py +32 -0
  93. pywargame/vassal/traits/dynamicproperty.py +112 -0
  94. pywargame/vassal/traits/globalcommand.py +55 -0
  95. pywargame/vassal/traits/globalhotkey.py +26 -0
  96. pywargame/vassal/traits/globalproperty.py +54 -0
  97. pywargame/vassal/traits/hide.py +67 -0
  98. pywargame/vassal/traits/label.py +76 -0
  99. pywargame/vassal/traits/layer.py +105 -0
  100. pywargame/vassal/traits/mark.py +20 -0
  101. pywargame/vassal/traits/mask.py +85 -0
  102. pywargame/vassal/traits/mat.py +26 -0
  103. pywargame/vassal/traits/moved.py +35 -0
  104. pywargame/vassal/traits/movefixed.py +51 -0
  105. pywargame/vassal/traits/nonrect.py +95 -0
  106. pywargame/vassal/traits/nostack.py +55 -0
  107. pywargame/vassal/traits/place.py +104 -0
  108. pywargame/vassal/traits/prototype.py +20 -0
  109. pywargame/vassal/traits/report.py +34 -0
  110. pywargame/vassal/traits/restrictaccess.py +28 -0
  111. pywargame/vassal/traits/restrictcommand.py +32 -0
  112. pywargame/vassal/traits/return.py +40 -0
  113. pywargame/vassal/traits/rotate.py +62 -0
  114. pywargame/vassal/traits/sendto.py +59 -0
  115. pywargame/vassal/traits/sheet.py +129 -0
  116. pywargame/vassal/traits/skel.py +9 -0
  117. pywargame/vassal/traits/stack.py +28 -0
  118. pywargame/vassal/traits/submenu.py +27 -0
  119. pywargame/vassal/traits/trail.py +61 -0
  120. pywargame/vassal/traits/trigger.py +72 -0
  121. pywargame/vassal/turn.py +272 -0
  122. pywargame/vassal/upgrade.py +191 -0
  123. pywargame/vassal/vmod.py +323 -0
  124. pywargame/vassal/vsav.py +100 -0
  125. pywargame/vassal/widget.py +358 -0
  126. pywargame/vassal/withtraits.py +634 -0
  127. pywargame/vassal/xml.py +4 -0
  128. pywargame/vassal/zone.py +399 -0
  129. pywargame/vassal.py +12500 -0
  130. pywargame/vmodpatch.py +12548 -0
  131. pywargame/vsavdump.py +12533 -0
  132. pywargame/vslmerge.py +13015 -0
  133. pywargame/wgexport.py +16689 -0
  134. pywargame/ztexport.py +14351 -0
  135. pywargame/zuntzu/__init__.py +5 -0
  136. pywargame/zuntzu/base.py +82 -0
  137. pywargame/zuntzu/collect.py +38 -0
  138. pywargame/zuntzu/countersheet.py +250 -0
  139. pywargame/zuntzu/dicehand.py +48 -0
  140. pywargame/zuntzu/exporter.py +936 -0
  141. pywargame/zuntzu/gamebox.py +154 -0
  142. pywargame/zuntzu/map.py +36 -0
  143. pywargame/zuntzu/piece.py +37 -0
  144. pywargame/zuntzu/scenario.py +208 -0
  145. pywargame/zuntzu/ztexp.py +115 -0
  146. pywargame-0.3.1.dist-info/METADATA +353 -0
  147. pywargame-0.3.1.dist-info/RECORD +150 -0
  148. pywargame-0.3.1.dist-info/WHEEL +5 -0
  149. pywargame-0.3.1.dist-info/licenses/LICENSE +5 -0
  150. pywargame-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1132 @@
1
+ ## BEGIN_IMPORTS
2
+ from common import VerboseGuard, Verbose
3
+ from vassal.buildfile import BuildFile
4
+ from vassal.documentation import Documentation
5
+ from vassal.traits import *
6
+ from vassal.base import *
7
+ from vassal.moduledata import ModuleData
8
+ from vassal.exporter import Exporter
9
+ ## END_IMPORTS
10
+
11
+ class CbExporter(Exporter):
12
+ def __init__(self,
13
+ d,
14
+ title = None,
15
+ version = None,
16
+ rules = None,
17
+ tutorial = None,
18
+ visible = False,
19
+ main_board = None,
20
+ map_regex = None,
21
+ vassalVersion = '3.6',
22
+ do_scenario = False):
23
+ self._d = d
24
+ self._title = title
25
+ self._version = version
26
+ self._rules = rules
27
+ self._tutorial = tutorial
28
+ self._visible = visible
29
+ self._vassalVersion = vassalVersion
30
+ self._flipped = False
31
+ self._mainBoard = main_board
32
+ self._mapRegex = map_regex
33
+ self._do_scenario = do_scenario
34
+ self._description = d['description']
35
+ self._placements = {}
36
+
37
+ # ----------------------------------------------------------------
38
+ def createModuleData(self):
39
+ '''Create the `moduleData` file in the module
40
+ '''
41
+ with VerboseGuard(f'Creating module data'):
42
+ self._moduleData = ModuleData()
43
+ data = self._moduleData.addData()
44
+ data.addVersion (version = self._version)
45
+ data.addVASSALVersion(version = self._vassalVersion)
46
+ data.addName (name = self._title)
47
+ data.addDescription (description = self._description)
48
+ data.addDateSaved ()
49
+
50
+ # ----------------------------------------------------------------
51
+ def addMoreDocumentation(self,doc):
52
+ pass
53
+
54
+ # ----------------------------------------------------------------
55
+ def addDocumentation(self):
56
+ '''Add documentation to the module. This includes rules,
57
+ key-bindings, and about elements.
58
+ '''
59
+ with VerboseGuard('Adding documentation') as v:
60
+ doc = self._game.addDocumentation()
61
+ if self._rules is not None:
62
+ self._vmod.addExternalFile(self._rules,'rules.pdf')
63
+ doc.addBrowserPDFFile(title = 'Show rules',
64
+ pdfFile = 'rules.pdf')
65
+
66
+ if self._tutorial is not None:
67
+ self._vmod.addExternalFile(self._tutorial,'tutorial.vlog')
68
+ doc.addTutorial(name = 'Tutorial',
69
+ logfile = 'tutorial.vlog',
70
+ launchOnStartup = True)
71
+
72
+ keys = [
73
+ ['Alt-A', '-', 'Show the charts panel'],
74
+ ['Alt-B', '-', 'Show the OOBs'],
75
+ ['Alt-C', '-', 'Show the counters panel'],
76
+ ['Alt-E', '-', 'Show the eliminated units'],
77
+ ['Alt-I', '-', 'Show/refresh inventory window'],
78
+ ['Alt-M', '-', 'Show map'],
79
+ ['Alt-T', '-', 'Increase turn track'],
80
+ ['Alt-Shift-T', '-', 'Decrease turn track'],
81
+ ['Alt-6', '-', 'Roll the dice'],
82
+ ['Ctrl-D', 'Board,Counter','Delete counters'],
83
+ ['Ctrl-E', 'Board,Counter','Eliminate counters'],
84
+ ['Ctrl-F', 'Board,Counter','Flip counters'],
85
+ ['Ctrl-M', 'Board,Counter','Toggle "moved" markers'],
86
+ ['Ctrl-O', 'Board', 'Hide/show counters'],
87
+ ['Ctrl-R', 'Board,Counter','Restore unit'],
88
+ ['Ctrl-T', 'Board,Counter','Toggle move trail'],
89
+ ['Ctrl-+', 'Board', 'Zoom in'],
90
+ ['Ctrl--', 'Board', 'Zoom out'],
91
+ ['Ctrl-=', 'Board', 'Select zoom'],
92
+ ['Ctrl-Shift-O', 'Board','Show overview map'],
93
+ ['←,→,↑↓','Board',
94
+ 'Scroll board left, right, up, down (slowly)'],
95
+ ['PnUp,PnDn','Board', 'Scroll board up/down (fast)'],
96
+ ['Ctrl-PnUp,Ctrl-PnDn','Board',
97
+ 'Scroll board left/right (fast)'],
98
+ ['Mouse-scroll up/down', 'Board',
99
+ 'Scroll board up//down'],
100
+ ['Shift-Mouse-scroll up/down','Board',
101
+ 'Scroll board right/leftown'],
102
+ ['Ctrl-Mouse-scroll up/down','Board','Zoom board out/in'],
103
+ ['Mouse-2', 'Board', 'Centre on mouse']]
104
+
105
+
106
+ self._vmod.addFile('help/keys.html',
107
+ Documentation.createKeyHelp(
108
+ keys,
109
+ title=self._d['title'],
110
+ version=self._d['version']))
111
+ doc.addHelpFile(title='Key bindings',fileName='help/keys.html')
112
+
113
+ self.addMoreDocumentation(doc)
114
+
115
+ # ----------------------------------------------------------------
116
+ def saveImages(self,d,vmod):
117
+ for serial,board in d['boards'].items():
118
+ self.saveSVG(board,vmod)
119
+
120
+ for piece in d['pieces']['map'].values():
121
+ if 'back' in piece: self._flipped = True
122
+
123
+ for which in ['front','back']:
124
+ if which not in piece:
125
+ continue
126
+
127
+ side = piece[which]
128
+ self.savePNG(side,vmod)
129
+
130
+ for mark in d['marks']['map'].values():
131
+ self.savePNG(mark,vmod)
132
+
133
+ # ----------------------------------------------------------------
134
+ def saveSVG(self,d,vmod):
135
+
136
+ with VerboseGuard(f'Saving rendered SVG image {d["filename"]}') as vg:
137
+ from wand.image import Image as WandImage
138
+ from pathlib import Path
139
+
140
+ path = Path(d['filename'])
141
+ filename = path.with_suffix('.png')
142
+ image = d['image']
143
+ size = d['size']
144
+ d['filename'] = filename
145
+ svg = image.encode()
146
+ svgout = path.with_suffix('.svg')
147
+ pngout = path.with_suffix('.png')
148
+ vmod.addFile('images/'+str(svgout),svg)
149
+ vg(f'Creating WandImage')
150
+
151
+ # We write the SVG to file, because Wand seems to have
152
+ # some issues reading from a bytes. The same applies for
153
+ # the command line
154
+ if vg:
155
+ vg(f'=== Write the SVG image to {svgout} ===')
156
+ with open(svgout,'w') as out:
157
+ out.write(image)
158
+
159
+ with WandImage() as wimage:
160
+ # Reading the bytes directly fails. It doesn't work
161
+ # from reading BytesIO stream either.
162
+ vg(f'Reading in SVG code')
163
+ wimage.read(filename=svgout,
164
+ # blob=image,#.encode(),
165
+ width=size[0],
166
+ height=size[1],
167
+ format='svg')
168
+
169
+ # Make a PNG
170
+ vg(f'Making blob')
171
+ png = wimage.make_blob('png')
172
+
173
+ # Write to the VMOD
174
+ vg(f'Write to VMOD')
175
+ vmod.addFile('images/'+str(pngout),png)
176
+ vg(f'Done writing SVG as PNG to VMOD')
177
+
178
+ # ----------------------------------------------------------------
179
+ def savePNG(self,d,vmod):
180
+ with VerboseGuard(f'Saving PNG image {d["filename"]}'):
181
+ from io import BytesIO
182
+
183
+ filename = d['filename']
184
+ img = d['image']
185
+ d['size'] = img.size
186
+ stream = BytesIO()
187
+ img.save(stream,format='PNG')
188
+ vmod.addFile('images/'+filename,stream.getvalue())
189
+
190
+ # ----------------------------------------------------------------
191
+ def addPrototypes(self,sides):
192
+ with VerboseGuard('Adding prototypes') as v:
193
+ protos = self._game.addPrototypes()
194
+ traits = [ReportTrait(key('E'),key('R'),key('M')),
195
+ TrailTrait(),
196
+ RotateTrait(),
197
+ DeleteTrait(),
198
+ MarkTrait(name='PieceLayer',value='Pieces'),
199
+ SendtoTrait(mapName = 'DeadMap',
200
+ boardName = f'Pool',
201
+ name = 'Eliminate',
202
+ key = key('E'),
203
+ restoreName = 'Restore',
204
+ restoreKey = key('R'),
205
+ description = 'Eliminate unit'),
206
+ BasicTrait()]
207
+
208
+ protos.addPrototype(name = f'Piece prototype',
209
+ description = f'Prototype for pieces',
210
+ traits = traits)
211
+ traits = [DeleteTrait(),
212
+ RotateTrait(),
213
+ MarkTrait(name='PieceLayer',value='Markers'),
214
+ BasicTrait()]
215
+ protos.addPrototype(name = f'Marker prototype',
216
+ description = f'Prototype for markers',
217
+ traits = traits)
218
+
219
+ for side in sides:
220
+ v(f'Making prototype trait for faction: "{side}"')
221
+ escside = side.replace('/','\\/')
222
+ traits = [MarkTrait(name='Faction',value=escside),
223
+ BasicTrait()]
224
+ protos.addPrototype(name = f'{side} prototype',
225
+ description = f'Prototype for {side}',
226
+ traits = traits)
227
+
228
+ # ----------------------------------------------------------------
229
+ def sanitise(self,txt):
230
+ for special in ['/',',',';','\\','\n','\t','\r']:
231
+ txt = txt.replace(special,' ')
232
+ return txt
233
+
234
+ # ----------------------------------------------------------------
235
+ def getToAdd(self,master,mapping,children):
236
+ toAdd = {}
237
+ for iden in children:
238
+ if iden not in master:
239
+ print(f'ID={iden} not found')
240
+ continue
241
+ if iden in mapping:
242
+ # Already added
243
+ continue
244
+
245
+ toAdd[iden] = master[iden]
246
+
247
+ return toAdd
248
+
249
+ # ----------------------------------------------------------------
250
+ def addPieceContainer(self,container,name):
251
+ #panel = container.addPanel(entryName = name,
252
+ # fixed = False,
253
+ # vert = True)
254
+ #return panel
255
+ panel = container.addPanel(entryName = name, fixed = False)
256
+ plist = panel.addList(entryName = f'{name}',
257
+ width=300,
258
+ height=300,
259
+ divider=150)
260
+ return plist
261
+
262
+ # ----------------------------------------------------------------
263
+ def addPieces(self,container,pieces,*pieceSets):
264
+ from pathlib import Path
265
+ with VerboseGuard('Adding pieces') as v:
266
+ # Store a map from pieceID to PieceSlot
267
+ self._pieceMap = {}
268
+ for sets in pieceSets:
269
+ for set in sets:
270
+ toAdd = self.getToAdd(pieces,self._pieceMap,set['pieces'])
271
+ if len(toAdd) <= 0:
272
+ # Nothing to add from this set
273
+ continue
274
+
275
+ name = set.get('name',set.get('description',None))
276
+ if name is None:
277
+ print(f'No name for set: {set}')
278
+ continue
279
+
280
+ plist = self.addPieceContainer(container, name)
281
+
282
+ for pieceID, piece in toAdd.items():
283
+ v(f'Make piece with faction: "{name}"')
284
+ escname = name.replace('/','\\/')
285
+ front = piece['front']['filename']
286
+ size = piece['front']['size']
287
+ back = piece.get('back',{}).get('filename',None)
288
+ traits = [MovedTrait(xoff=size[0]//2,
289
+ yoff=-size[1]//2),
290
+ MarkTrait(name='Faction',value=escname),
291
+ MarkTrait(name='PieceLayer',value='Pieces'),
292
+ PrototypeTrait(name='Piece prototype')]
293
+
294
+ if back is not None:
295
+ traits.extend([
296
+ LayerTrait(images = [front, back],
297
+ newNames = ['','Reduced +'],
298
+ activateName = '',
299
+ decreaseName = '',
300
+ increaseName = 'Flip',
301
+ increaseKey = key('F'),
302
+ decreaseKey = '',
303
+ name = 'Step'),
304
+ ReportTrait(key('F'))])
305
+ if 'description' in piece:
306
+ desc = self.sanitise(piece['description'])
307
+ traits.append(MarkTrait(name='description',
308
+ value=desc))
309
+
310
+ pname = Path(front).stem.replace('_front','')
311
+ gpid = self._game.nextPieceSlotId()
312
+ traits.extend([BasicTrait(name = pname,
313
+ filename = front,
314
+ gpid = gpid)])
315
+
316
+ ps = plist.addPieceSlot(entryName = pname,
317
+ gpid = gpid,
318
+ traits = traits,
319
+ width = size[0],
320
+ height = size[1])
321
+
322
+ # print(f'Piece: {pieceID}')
323
+ self._pieceMap[pieceID] = ps
324
+
325
+ # ----------------------------------------------------------------
326
+ def addMarks(self,container,marks,*markSets):
327
+ from pathlib import Path
328
+ with VerboseGuard('Adding marks') as v:
329
+ # Store a map from markID to PieceSlot
330
+ self._markMap = {}
331
+ for sets in markSets:
332
+ for set in sets:
333
+ toAdd = self.getToAdd(marks,self._markMap,set['marks'])
334
+ if len(toAdd) <= 0:
335
+ # Nothing to add from this set
336
+ continue
337
+
338
+ name = set.get('name',set.get('description',None))
339
+ if name is None:
340
+ print(f'No name for set: {set}')
341
+ continue
342
+
343
+ mlist = self.addPieceContainer(container, name)
344
+
345
+ for markID, mark in toAdd.items():
346
+ file = mark['filename']
347
+ size = mark['size']
348
+ traits = [PrototypeTrait(name='Mark prototype'),
349
+ MarkTrait(name='PieceLayer',
350
+ value='Markers')]
351
+ if 'description' in mark:
352
+ desc = self.sanitise(mark['description'])
353
+ traits.append(MarkTrait(name='description',
354
+ value=desc))
355
+
356
+ mname = Path(file).stem
357
+ gpid = self._game.nextPieceSlotId()
358
+ traits.extend([BasicTrait(name = mname,
359
+ filename = file,
360
+ gpid = gpid)])
361
+
362
+ ps = mlist.addPieceSlot(entryName = mname,
363
+ gpid = gpid,
364
+ traits = traits,
365
+ width = size[0],
366
+ height = size[1])
367
+
368
+ # print(f'Mark: {markID}')
369
+ self._markMap[markID] = ps
370
+
371
+ # ----------------------------------------------------------------
372
+ def addBoards(self,boards):
373
+ with VerboseGuard(f'Adding boards '
374
+ f'{[n["gboard"]["name"] for n in boards]}') as v:
375
+ first = True
376
+ names = []
377
+ for board in boards:
378
+ names.append(self.addBoard(first,**board))
379
+ first = False
380
+
381
+ filtered = [n for n in names
382
+ if n != '' and n != self._mainBoard]
383
+ v(f'Filtered set of maps: {filtered}')
384
+ if len(filtered) > 1:
385
+ self._game.addMenu(description = 'Maps',
386
+ text = 'Maps',
387
+ tooltip = 'Show or hide maps',
388
+ icon = '/images/map.gif',
389
+ menuItems = filtered)
390
+
391
+ # ----------------------------------------------------------------
392
+ def addMapDefaults(self,map):
393
+ # map.addHidePiecesButton()
394
+ # Basics
395
+ map.addStackMetrics()
396
+ map.addImageSaver()
397
+ map.addTextSaver()
398
+ map.addForwardToChatter()
399
+ map.addMenuDisplayer()
400
+ map.addMapCenterer()
401
+ map.addStackExpander()
402
+ map.addCounterDetailViewer()
403
+ map.addPieceMover()
404
+ map.addKeyBufferer()
405
+ map.addForwardKeys()
406
+ map.addScroller()
407
+ map.addSelectionHighlighters()
408
+ map.addHighlightLastMoved()
409
+ map.addZoomer()
410
+
411
+ # ----------------------------------------------------------------
412
+ def addMapLayers(self,map):
413
+ layerDesc = {'Pieces': {'t': 'Pieces', 'i': ''},
414
+ 'Markers': {'t': 'Markers','i': ''}}
415
+ layers = map.addLayers(property = 'PieceLayer',
416
+ layerOrder = layerDesc)
417
+ for layerTitle, layerData in layerDesc.items():
418
+ layers.addControl(name = layerTitle,
419
+ tooltip = f'Toggle display of {layerTitle}',
420
+ text = f'Toggle {layerData["t"]}',
421
+ icon = layerData['i'],
422
+ layers = [layerTitle])
423
+ layers.addControl(name = 'Show all',
424
+ tooltip = 'Show all',
425
+ text = 'Show all',
426
+ command = LayerControl.ENABLE,
427
+ layers = list(layerDesc.keys()))
428
+ map.addMenu(description = 'Toggle layers',
429
+ text = '',
430
+ tooltip = 'Toggle display of layers',
431
+ icon = '/images/inventory.gif',
432
+ menuItems = ([f'Toggle {ln["t"]}'
433
+ for ln in layerDesc.values()]
434
+ +['Show all']))
435
+ # ----------------------------------------------------------------
436
+ def addBoard(self,first,gboard,color=0xFF0000,pieces=None,indicators=None):
437
+ with VerboseGuard(f'Adding board {gboard["name"]}') as v:
438
+ mapName = gboard['name']
439
+ map = self._game.addMap(
440
+ mapName = mapName,
441
+ buttonName = '' if first else mapName,
442
+ hotkey = '' if first else key('M',ALT),
443
+ launch = not first
444
+ )
445
+ self.addMapDefaults(map)
446
+ self.addMapLayers(map)
447
+ map.addGlobalMap()
448
+
449
+ map.addMassKey(name='Eliminate',
450
+ buttonHotkey = key('E'),
451
+ hotkey = key('E'),
452
+ icon = '/icons/16x16/edit-undo.png',
453
+ tooltip = 'Eliminate selected units')
454
+ map.addMassKey(name='Delete',
455
+ buttonHotkey = key('D'),
456
+ hotkey = key('D'),
457
+ icon = '/icons/16x16/no.png',
458
+ tooltip = 'Delete selected units')
459
+ if self._flipped:
460
+ map.addMassKey(name='Flip',
461
+ buttonHotkey = key('F'),
462
+ hotkey = key('F'),
463
+ icon = '/images/Undo16.gif',
464
+ tooltip = 'Flip selected units')
465
+
466
+
467
+ size = gboard['size']
468
+ picker = map.addBoardPicker()
469
+ board = picker.addBoard(name = gboard['name'],
470
+ image = gboard['filename'],
471
+ width = size[0],
472
+ height = size[1])
473
+ zoned = board.addZonedGrid()
474
+ zoned.addHighlighter()
475
+ zone = zoned.addZone(name = 'Full',
476
+ useParentGrid = False,
477
+ path=(f'{0},{0};' +
478
+ f'{size[0]},{0};' +
479
+ f'{size[0]},{size[1]};' +
480
+ f'{0},{size[1]}'))
481
+
482
+ self.addGrid(zone,gboard,color)
483
+ self.addAtStart(map,pieces)
484
+ self.addAtStart(map,indicators)
485
+
486
+ return mapName
487
+
488
+ # ----------------------------------------------------------------
489
+ def addCharts(self,gcharts):
490
+ if len(gcharts) <= 0:
491
+ return
492
+
493
+ with VerboseGuard(f'Adding Charts '
494
+ f'{[n["gboard"]["name"] for n in gcharts]}') as v:
495
+ charts = \
496
+ self._game.addChartWindow(name='Charts',
497
+ hotkey = key('A',ALT),
498
+ description = 'Charts',
499
+ text = '',
500
+ icon = '/images/chart.gif',
501
+ tooltip = 'Show/hide Charts')
502
+ tabs = charts.addTabs(entryName='Charts')
503
+
504
+ for chart in gcharts:
505
+ widget = tabs.addMapWidget(entryName=chart['gboard']['name'])
506
+ self.addChart(widget,**chart)
507
+
508
+ # ----------------------------------------------------------------
509
+ def addChart(self,widget,gboard,color=0xFF0000,pieces=None,indicators=None):
510
+ with VerboseGuard(f'Adding Chart {gboard["name"]}') as v:
511
+ map = widget.addWidgetMap(mapName = gboard['name'],
512
+ markMoved = 'Never',
513
+ hotkey = '')
514
+ self.addMapDefaults(map)
515
+ self.addMapLayers(map)
516
+
517
+ size = gboard['size']
518
+ picker = map.addPicker()
519
+ board = picker.addBoard(name = gboard['name'],
520
+ image = gboard['filename'])
521
+ zoned = board.addZonedGrid()
522
+ zoned.addHighlighter()
523
+ zone = zoned.addZone(name = 'Full',
524
+ useParentGrid = False,
525
+ path=(f'{0},{0};' +
526
+ f'{size[0]},{0};' +
527
+ f'{size[0]},{size[1]};' +
528
+ f'{0},{size[1]}'))
529
+
530
+ self.addGrid(zone,gboard,color)
531
+ self.addAtStart(map,pieces)
532
+ self.addAtStart(map,indicators)
533
+
534
+ # ----------------------------------------------------------------
535
+ def getNumParam(self,col,gboard,geom):
536
+ rcol = (col >> 16) & 0xFF
537
+ gcol = (col >> 8) & 0xFF
538
+ bcol = (col >> 0) & 0XFF
539
+ fid = gboard['numbering']['order'].lower()
540
+ sid = 'h' if fid=='v' else 'v'
541
+ side = 'sideways' in geom['shape']
542
+ hex = 'hexagon' in geom['shape']
543
+ d = {'first': fid.upper(),
544
+ 'color': rgb(rcol,gcol,bcol),
545
+ 'visible': self._visible,
546
+ 'vOff': gboard['rows']['offset']+1,
547
+ 'vDescend': gboard['rows']['inverted'],
548
+ 'hOff': gboard['columns']['offset']+1, # CB start at 1
549
+ 'vOff': gboard['columns']['inverted']+1, # VSL start at 0
550
+ 'hLeading': 1 if gboard['numbering']['padding'] else 0,
551
+ 'vLeading': 1 if gboard['numbering']['padding'] else 0,
552
+ 'stagger': geom['stagger'] == 'in',
553
+ f'{fid}Type': gboard['numbering']['first'],
554
+ f'{sid}Type': 'N'
555
+ }
556
+ # Coord 0 -> A in VSL, Coord 1 -> A in CB
557
+ d[f'{fid}Off'] += 0 if gboard['numbering']['first'] != 'A' else -1
558
+ return d
559
+
560
+ # ----------------------------------------------------------------
561
+ def addGrid(self,zone,gboard,col=0x000000):
562
+ geom = gboard.get('cells',{}).get('geometry',None)
563
+ if geom is None:
564
+ return
565
+
566
+ if geom['shape'] == 'rectangle':
567
+ self.addRegularGrid(zone,gboard,geom,col)
568
+ elif 'brick' in geom['shape']:
569
+ self.addRegionGrid(zone,gboard,geom,col)
570
+ elif 'hexagon' in geom['shape']:
571
+ self.addHexGrid(zone,gboard,geom,col)
572
+
573
+
574
+
575
+ # ----------------------------------------------------------------
576
+ def addHexGrid(self,zone,gboard,geom,col=0x000000):
577
+ from math import sin, pi
578
+ with VerboseGuard(f'Adding hex grid') as v:
579
+ size = geom['size']
580
+ side = 'sideways' in geom['shape']
581
+ npar = self.getNumParam(col,gboard,geom)
582
+ dx = int(size[0] * sin(pi/3) ** 2)
583
+ dy = size[1]
584
+ x0 = size[0]//2
585
+ y0 = size[1]//2 + (0 if geom['stagger']=='out' else dy//2)
586
+ if side:
587
+ dy = size[0]
588
+ dx = dy * sin(pi/3)
589
+ x0, y0 = y0, x0
590
+ npar['first'] = 'V' if npar['first'] == 'H' else 'H'
591
+
592
+ v(f'Size = {size}, side = {side} dx={dx} dy={dy}')
593
+ grid = zone.addHexGrid(dx = dx,
594
+ dy = dy,
595
+ x0 = x0,
596
+ y0 = y0,
597
+ sideways = side,
598
+ visible = self._visible,
599
+ color = npar['color'],
600
+ edgesLegal = True,
601
+ cornersLegal = True,
602
+ snapTo = True
603
+ #gboard['snap']['enable']
604
+ )
605
+ grid.addNumbering(**npar)
606
+
607
+ # ----------------------------------------------------------------
608
+ def addRegularGrid(self,zone,gboard,geom,col=0x000000):
609
+ size = geom['size']
610
+ npar = self.getNumParam(col,gboard,geom)
611
+ grid = zone.addSquareGrid(dx = size[0],
612
+ dy = size[1],
613
+ x0 = size[0]//2,
614
+ y0 = size[1]//2,
615
+ visible = self._visible,
616
+ color = npar['color'],
617
+ snapTo = gboard['snap']['enable'])
618
+ grid.addNumbering(**npar)
619
+
620
+
621
+
622
+ # ----------------------------------------------------------------
623
+ def addRegionGrid(self,zone,gboard,geom):
624
+ grid = zone.addRegionGrid(snapto = True,
625
+ visible = self._visible)
626
+
627
+ for row in gboard['cells']['list']:
628
+ for cell in row:
629
+ name = f'r{cell["row"]}_{cell["column"]}'
630
+ pixel = cell['pixel']
631
+ grid.addRegion(name = name,
632
+ originx = pixel[0],
633
+ originy = pixel[1],
634
+ alsoPiece = False)
635
+ # ----------------------------------------------------------------
636
+ def addAtStart(self,map,pieces):
637
+ with VerboseGuard(f'Adding at-start to {map["mapName"]}') as g:
638
+ toAdd = {}
639
+ mapName = map['mapName']
640
+
641
+ for piece in pieces:
642
+ pmap = None
643
+ if piece['type'] == 'Piece':
644
+ pmap = self._pieceMap
645
+ elif piece['type'] == 'Mark':
646
+ pmap = self._markMap
647
+ else:
648
+ continue
649
+
650
+
651
+ iden = piece['id']
652
+ pm = pmap.get(iden,None)
653
+ #print(piece,pm)
654
+ if pm is None:
655
+ print(f'Cannot fine {piece["type"]} {iden}')
656
+ print(list(pmap.keys()))
657
+ continue
658
+
659
+ grid = tuple(piece['grid'])
660
+ x = piece['pixel']['x']
661
+ y = piece['pixel']['y']
662
+ if grid not in toAdd:
663
+ toAdd[grid] = {'center': (x,y),
664
+ 'pieces': [] }
665
+ toAdd[grid]['pieces'].append(pm)
666
+
667
+ if self._do_scenario:
668
+ mapped = {
669
+ a['center']: a['pieces'] for a in toAdd.values()}
670
+
671
+ if mapName not in self._placements:
672
+ self._placements[mapName] = {}
673
+
674
+ mapPlacements = self._placements[mapName]
675
+
676
+ # We have to do this carefully, because we may have
677
+ # already added something to the placements for this
678
+ # map and specific locations.
679
+ for coord, pieces in mapped.items():
680
+ if coord in mapPlacements:
681
+ mapPlacements[coord].extend(pieces)
682
+ else:
683
+ mapPlacements[coord] = pieces
684
+
685
+ else:
686
+ for grid, dpieces in toAdd.items():
687
+ center = dpieces['center']
688
+ name = f'{grid[0]:02d}{grid[1]:02d}'
689
+ atstart = map.addAtStart(name=name,
690
+ useGridLocation=False,
691
+ owningBoard=mapName,
692
+ x = center[0],
693
+ y = center[1])
694
+ atstart.addPieces(*dpieces['pieces'])
695
+
696
+ # ----------------------------------------------------------------
697
+ def addDeadMap(self,sides):
698
+ '''Add a "Dead Map" element to the module
699
+ '''
700
+ name = 'DeadMap'
701
+ with VerboseGuard(f'Adding board {name}') as v:
702
+ map = self._game.addMap(mapName = name,
703
+ buttonName = '',
704
+ markMoved = 'Never',
705
+ launch = True,
706
+ icon = '/images/playerAway.gif',
707
+ allowMultiple = True,
708
+ hotkey = key('E',ALT))
709
+ # Basics
710
+ map.addStackMetrics()
711
+ map.addImageSaver()
712
+ map.addTextSaver()
713
+ map.addForwardToChatter()
714
+ map.addMenuDisplayer()
715
+ map.addMapCenterer()
716
+ map.addStackExpander()
717
+ map.addPieceMover()
718
+ map.addKeyBufferer()
719
+ map.addSelectionHighlighters()
720
+ map.addHighlightLastMoved()
721
+ map.addZoomer()
722
+
723
+ map.addMassKey(name='Restore',
724
+ buttonHotkey = key('R'),
725
+ hotkey = key('R'),
726
+ icon = '/images/Undo16.gif',
727
+ tooltip = 'Restore selected units')
728
+
729
+ if sides is None or len(sides) <= 0:
730
+ sides = ['A']
731
+ picker = map.addBoardPicker()
732
+ picker.addSetup(maxColumns=len(sides),mapName=name,
733
+ boardNames=[s+' pool' for s in sides])
734
+
735
+ for i, s in enumerate(sides):
736
+ v(f'Adding {s} pool')
737
+ color = [0,0,0,64]
738
+ color[i % 3] = 255
739
+ w = 400
740
+ h = 400
741
+ c = rgba(*color)
742
+ board = picker.addBoard(name = f'{s} pool',
743
+ image = '',
744
+ width = w,
745
+ height = h,
746
+ color = c)
747
+
748
+ # ====================================================================
749
+ class GBXExporter(CbExporter):
750
+ def __init__(self,
751
+ d,
752
+ title = None,
753
+ version = None,
754
+ rules = None,
755
+ tutorial = None,
756
+ visible = False,
757
+ main_board = None,
758
+ map_regex = None,
759
+ vassalVersion = '3.6',
760
+ do_scenario = False):
761
+ super(GBXExporter,self).__init__(d,
762
+ title = title,
763
+ version = version,
764
+ rules = rules,
765
+ tutorial = tutorial,
766
+ visible = visible,
767
+ main_board = main_board,
768
+ map_regex = map_regex,
769
+ vassalVersion = vassalVersion,
770
+ do_scenario = do_scenario)
771
+
772
+ # ----------------------------------------------------------------
773
+ def setup(self):
774
+ self.saveImages(self._d,self._vmod)
775
+ self._title = self._d['title']
776
+ self._version = self._d['version']
777
+ self._description = self._d['description']
778
+
779
+ # ----------------------------------------------------------------
780
+ def createBuildFile(self,
781
+ ignores = ['all',
782
+ 'common',
783
+ 'marker',
784
+ 'markers',
785
+ ' ']):
786
+ with VerboseGuard(f'Creating build file') as v:
787
+ self._build = BuildFile() # 'buildFile.xml')
788
+ self._game = self._build.addGame(name = self._title,
789
+ version = self._version,
790
+ description = self._description)
791
+ self.addDocumentation()
792
+ self._game.addBasicCommandEncoder()
793
+
794
+
795
+ # ====================================================================
796
+ class GSNExporter(CbExporter):
797
+ def __init__(self,
798
+ d,
799
+ title = None,
800
+ version = None,
801
+ rules = None,
802
+ tutorial = None,
803
+ visible = False,
804
+ main_board = None,
805
+ map_regex = None,
806
+ vassalVersion = '3.6',
807
+ do_scenario = False):
808
+ super().__init__(d,
809
+ title = title,
810
+ version = version,
811
+ rules = rules,
812
+ tutorial = tutorial,
813
+ visible = visible,
814
+ main_board = main_board,
815
+ map_regex = map_regex,
816
+ vassalVersion = vassalVersion,
817
+ do_scenario = do_scenario)
818
+
819
+ # ----------------------------------------------------------------
820
+ def setup(self):
821
+ with VerboseGuard('Setup') as v:
822
+ from pathlib import Path
823
+
824
+ self.saveImages(self._d['gamebox'],self._vmod)
825
+ self.setupTitle()
826
+
827
+ def setupTitle(self):
828
+ with VerboseGuard('Setup titles and such') as v:
829
+ v(f'Before: '
830
+ f'title={self._title}, '
831
+ f'version={self._version}, '
832
+ f'description={self._description}')
833
+ v(f'Game box: '
834
+ f'title={self._d["gamebox"]["title"]}, '
835
+ f'version={self._d["gamebox"]["version"]}, '
836
+ f'description={self._d["gamebox"]["description"]}')
837
+ v(f'Scenario: '
838
+ f'title={self._d["title"]}, '
839
+ f'version={self._d["version"]}, '
840
+ f'description={self._d["description"]}')
841
+
842
+ if self._title is None or self._title == '':
843
+ # If we are making a scenario, then do not take the
844
+ # title from the scenario file
845
+ self._title = None if self._do_scenario else self._d['title']
846
+ if self._title is None or self._title == '':
847
+ self._title = self._d['gamebox']['title']
848
+ if self._title is None or self._title == '':
849
+ from random import choice
850
+ from string import ascii_lowercase
851
+ self._title = Path(self._vmod.fileName()).stem
852
+ # self._title = ''.join([choice(ascii_lowercase)
853
+ # for _ in range(32)])
854
+ if not self._do_scenario:
855
+ self._d['title'] = self._title
856
+
857
+ if self._version is None or self._version == '':
858
+ # If we are doing a scenario, do not take the version
859
+ # from the scenario file.
860
+ self._version = (None if self._do_scenario else
861
+ self._d['version'])
862
+ if self._version is None or self._version == '':
863
+ self._version = self._d['gamebox']['version']
864
+ if self._version is None or self._version == '':
865
+ self._version = '0.0'
866
+ if not self._do_scenario:
867
+ self._d['version'] = self._version
868
+
869
+ if self._do_scenario:
870
+ # Take overall description form game box
871
+ self._description = self._d['gamebox']['description']
872
+ v(f'After: '
873
+ f'title={self._title}, '
874
+ f'version={self._version}, '
875
+ f'description={self._description}')
876
+
877
+ # ----------------------------------------------------------------
878
+ def createBuildFile(self,
879
+ ignores = '(.*markers?|all|commons|[ ]+)'):
880
+ '''Create the XML buildFile.xml
881
+
882
+ Parameters
883
+ ----------
884
+ ignores : str
885
+ Regular expression to match ignored categories for factions
886
+ determination. Python's re.fullmatch is applied to this
887
+ regular exression against chit categories. If the pattern
888
+ is matched, then the chit is not considered to belong to a
889
+ faction.
890
+ '''
891
+ with VerboseGuard(f'Creating build file') as v:
892
+ desc = None if self._do_scenario else self._d['description']
893
+ if desc is None or desc == '':
894
+ desc = self._d['gamebox']['description']
895
+ if len(desc) > 32:
896
+ desc = desc[:32]
897
+ self._build = BuildFile() # 'buildFile.xml')
898
+ self._game = self._build.addGame(name = self._title,
899
+ version = self._version,
900
+ description = desc)
901
+
902
+ self._sides = self._d['players']
903
+ self._sides = [p.replace(' player','').replace(' Player','')
904
+ for p in self._sides]
905
+
906
+ if not self._sides:
907
+ self._sides = [s['description'] for s in
908
+ self._d['gamebox']['pieces']['sets']]
909
+
910
+ v(f'Sides: {self._sides}')
911
+
912
+ v(f'Adding documentation')
913
+ self.addDocumentation()
914
+
915
+ v(f'Adding command encoder')
916
+ self._game.addBasicCommandEncoder()
917
+
918
+ v(f'Adding Global options')
919
+ go = self._game.addGlobalOptions(
920
+ autoReport = 'Use Preferences Setting',
921
+ centerOnMove = 'Use Preferences Setting',
922
+ nonOwnerUnmaskable = 'Use Preferences Setting',
923
+ playerIdFormat = '$playerName$')
924
+ go.addOption(name='undoHotKey',value=key('Z'))
925
+
926
+ v(f'Adding player roster {self._sides}')
927
+ roster = self._game.addPlayerRoster()
928
+ for side in self._sides:
929
+ roster.addSide(side)
930
+
931
+ v(f'Adding global properties')
932
+ glob = self._game.addGlobalProperties()
933
+ glob.addProperty(name='TurnTracker.defaultDocked',
934
+ initialValue=True)
935
+
936
+ v(f'Adding notes')
937
+ self._game.addNotes()
938
+
939
+ v(f'Adding turn track')
940
+ turns = self._game.addTurnTrack(name='Turn',
941
+ counter={
942
+ 'property': 'Turn',
943
+ 'phases': {
944
+ 'property': 'Phase',
945
+ 'names': self._sides } })
946
+
947
+
948
+ self.addPrototypes(self._sides)
949
+ self.addPiecesMarks()
950
+ self.addBoards()
951
+ self.addCharts()
952
+ self.addDeadMap(self._sides)
953
+ self.addScenario()
954
+
955
+
956
+ #
957
+ def addMoreDocumentation(self,doc):
958
+ desc = f'''<html><body>
959
+ <h1>Module</h1>
960
+ <p>
961
+ This module was created from CyberBoard Scenario
962
+ {self._d["title"]} version {self._d["version"]} and game box
963
+ {self._d["gamebox"]["title"]} version {self._d["gamebox"]["version"]} '
964
+ 'by the Python script <code>gsnexport.py</code> available from
965
+ </p>
966
+ <pre>
967
+ htps://gitlab.com/wargames_tex/pywargame
968
+ </pre>
969
+ <h1>Game</h1>
970
+ <table>
971
+ <tr><td>Title:</td><td>{self._d["gamebox"]["title"]}</td></tr>
972
+ <tr><td>Version:</td><td>{self._d["gamebox"]["version"]}</td></tr>
973
+ <tr><td>Author:</td><td>{self._d["gamebox"]["author"]}</td></tr>
974
+ <tr><td>Description:</td><td>{self._d["gamebox"]["description"]}</td></tr>
975
+ </table>
976
+ <h1>Scenario</h1>
977
+ <table>
978
+ <tr><td>Title:</td><td>{self._d["title"]}</td></tr>
979
+ <tr><td>Version:</td><td>{self._d["version"]}</td></tr>
980
+ <tr><td>Author:</td><td>{self._d["author"]}</td></tr>
981
+ <tr><td>Description:</td><td>{self._d["description"]}</td></tr>
982
+ </table>
983
+ </body></html>;'''
984
+ self._vmod.addFile('help/description.html',desc)
985
+ doc.addHelpFile(title='Description',fileName='help/description.html')
986
+
987
+
988
+ # ----------------------------------------------------------------
989
+ def addPiecesMarks(self):
990
+ with VerboseGuard(f'Adding pieces and marks') as v:
991
+ window = self._game.addPieceWindow(name = 'Counters',
992
+ hotkey = key('C',ALT))
993
+ combo = window.addCombo(entryName='Counters')
994
+ self.addPieces(combo,
995
+ self._d['gamebox']['pieces']['map'],
996
+ self._d['trays'],
997
+ self._d['gamebox']['pieces']['sets'])
998
+ self.addMarks(combo,
999
+ self._d['gamebox']['marks']['map'],
1000
+ self._d['gamebox']['marks']['sets'])
1001
+ # ----------------------------------------------------------------
1002
+ def boardMatch(self,name):
1003
+ if self._mapRegex is None:
1004
+ return False
1005
+
1006
+ from re import match
1007
+
1008
+ ret = match(self._mapRegex, name) is not None
1009
+
1010
+ return ret
1011
+
1012
+ # ----------------------------------------------------------------
1013
+ def addBoards(self):
1014
+ with VerboseGuard(f'Adding boards') as v:
1015
+ toAdd = []
1016
+ for serial, sboard in self._d['boards'].items():
1017
+ board = self._d['gamebox']['boards'].get(serial,None)
1018
+ if board is None:
1019
+ continue
1020
+ # print('BOARD (gsn)',board)
1021
+ #
1022
+ # If not set to start map in GBX, and name does not
1023
+ # the user specified main board and board name does
1024
+ # not match the board regular expression, then
1025
+ # continue on (the board will be made chart instead).
1026
+ if not sboard['onload'] and \
1027
+ board['name'] != self._mainBoard and \
1028
+ not self.boardMatch(board['name']):
1029
+ continue
1030
+
1031
+ # Here, we add pieces and indicators as at-start on
1032
+ # the map. Perhaps we should _not_ do that, and write
1033
+ # a scenario file instead.
1034
+ d = {'gboard': board,
1035
+ 'color': sboard['grid']['color'],
1036
+ 'pieces': sboard['pieces'],
1037
+ 'indicators': sboard['indicators'] }
1038
+ if board['name'] == self._mainBoard:
1039
+ # If this is the main board, as selected by user,
1040
+ # then put first in the queue.
1041
+ toAdd.insert(0,d)
1042
+ else:
1043
+ toAdd.append(d)
1044
+
1045
+ super(GSNExporter,self).addBoards(toAdd)
1046
+
1047
+ # ----------------------------------------------------------------
1048
+ def addCharts(self):
1049
+ with VerboseGuard(f'Adding Charts') as v:
1050
+ toAdd = []
1051
+ for serial, sboard in self._d['boards'].items():
1052
+ board = self._d['gamebox']['boards'].get(serial,None)
1053
+ if board is None:
1054
+ continue
1055
+ # print('BOARD (gbx)',board)
1056
+ #
1057
+ # If the board is set to start at load on GBX, or the
1058
+ # name of the board matches the main board name set by
1059
+ # user, or board name matched the user-defined board
1060
+ # regular expression, the skip this board (was added
1061
+ # previously as a board).
1062
+ if sboard['onload'] or \
1063
+ board['name'] == self._mainBoard or \
1064
+ self.boardMatch(board['name']):
1065
+ continue
1066
+
1067
+ # Here, we add pieces and indicators as at-start on
1068
+ # the map. Perhaps we should _not_ do that, and write
1069
+ # a scenario file instead.
1070
+ d = {'gboard': board,
1071
+ 'color': sboard['grid']['color'],
1072
+ 'pieces': sboard['pieces'],
1073
+ 'indicators': sboard['indicators'] }
1074
+ if board['name'] == self._mainBoard:
1075
+ # If this is the main board, as selected by user,
1076
+ # then put first in the queue.
1077
+ #
1078
+ # Should this be done for charts? Probably not.
1079
+ toAdd.insert(0,d)
1080
+ else:
1081
+ toAdd.append(d)
1082
+
1083
+ super(GSNExporter,self).addCharts(toAdd)
1084
+
1085
+ # ----------------------------------------------------------------
1086
+ def addScenario(self):
1087
+ if not self._do_scenario or len(self._placements) <= 0:
1088
+ # Nothing to do
1089
+ return
1090
+
1091
+ from pathlib import Path
1092
+
1093
+ menu = self._game.addPredefinedSetup(name = 'Setups',
1094
+ isMenu = True)
1095
+
1096
+ menu.addPredefinedSetup(name = 'No setup',
1097
+ file = '',
1098
+ useFile = False,
1099
+ description = 'No initial setup')
1100
+
1101
+ with VerboseGuard(f'Adding a scenario {self._d["title"]}') as v:
1102
+ vsav = VSav(self._build,self._vmod)
1103
+ save = vsav.addSaveFile()
1104
+
1105
+ # from pprint import pprint
1106
+ # pprint(self._placements)
1107
+
1108
+ for mapName,placements in self._placements.items():
1109
+
1110
+ save.addNoGrid(mapName, placements)
1111
+
1112
+ # print('\n'.join(save.getLines()))
1113
+
1114
+ savfile = self._d['title'].replace(' ','_')+'.vsav'
1115
+ vsav.run(savename = savfile,
1116
+ description = self._d['description'])
1117
+
1118
+ menu.addPredefinedSetup(name=self._d['title'],
1119
+ useFile=True,
1120
+ file=savfile,
1121
+ description=self._d['description'])
1122
+
1123
+
1124
+ # ====================================================================
1125
+
1126
+ #
1127
+ # EOF
1128
+ #
1129
+
1130
+
1131
+
1132
+