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,936 @@
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
+ from vassal.mapelements import LayerControl
10
+ from vassal.vsav import VSav
11
+ from base import ZTImage
12
+ ## END_IMPORTS
13
+
14
+
15
+ class ZTExporter(Exporter):
16
+ def __init__(self,
17
+ gamebox,
18
+ version = None,
19
+ vassalVersion = '3.6',
20
+ allmaps = False,
21
+ mainMap = None,
22
+ nrotations = 6,
23
+ symbolic_dice = True):
24
+ self._gamebox = gamebox
25
+ self._version = '0.0.1' if version is None else version
26
+ self._vassalVersion = vassalVersion
27
+ self._allmaps = allmaps
28
+ self._main_map = mainMap
29
+ self._n_rotations = nrotations
30
+ self._symbolic_dice = symbolic_dice
31
+
32
+ # ----------------------------------------------------------------
33
+ def setup(self):
34
+ with VerboseGuard('Saving images'):
35
+ from pathlib import Path
36
+
37
+
38
+ # ----------------------------------------------------------------
39
+ def createModuleData(self):
40
+ with VerboseGuard('Creating module data'):
41
+ self._moduleData = ModuleData()
42
+ data = self._moduleData.addData()
43
+ data.addVersion (version='0.0.1')
44
+ data.addVASSALVersion(version=self._vassalVersion)
45
+ data.addName (name =self._gamebox._name)
46
+ data.addDescription (description=self._gamebox._desc)
47
+ data.addDateSaved ()
48
+
49
+ # ----------------------------------------------------------------
50
+ def createBuildFile(self,
51
+ ignores = ['all',
52
+ 'common',
53
+ 'marker',
54
+ 'markers',
55
+ ' ']):
56
+ with VerboseGuard(f'Creating build file') as v:
57
+ self._build = BuildFile() # 'buildFile.xml')
58
+ self._game = self._build.addGame(
59
+ name = self._gamebox._name,
60
+ version = self._version,
61
+ description = self._gamebox._desc)
62
+
63
+ v(f'Adding description')
64
+ self.addDocumentation()
65
+
66
+ v(f'Adding command encoder')
67
+ self._game.addBasicCommandEncoder()
68
+
69
+ v(f'Adding Global options')
70
+ go = self._game.addGlobalOptions(
71
+ autoReport = 'Use Preferences Setting',
72
+ centerOnMove = 'Use Preferences Setting',
73
+ nonOwnerUnmaskable = 'Use Preferences Setting',
74
+ playerIdFormat = '$playerName$')
75
+ go.addOption(name='undoHotKey',value=key('Z'))
76
+
77
+ v(f'Adding dice hands')
78
+ self.addDiceHands()
79
+
80
+ v(f'Adding prototypes')
81
+ self.addPrototypes()
82
+
83
+ v(f'Adding pieces')
84
+ self.addPieces()
85
+
86
+ v(f'Fixing up map locations')
87
+ self.fixMaps()
88
+
89
+ v(f'Adding maps')
90
+ self.addMaps()
91
+
92
+ v(f'Adding counter sheets')
93
+ self.addCounterSheets()
94
+
95
+ v(f'Adding scenarios')
96
+ self.addScenarios()
97
+
98
+ def fixMaps(self):
99
+ # Scale all scenarios to target resolution before anything else
100
+ for sc in self._gamebox._scenarios:
101
+ sc.scale(ZTImage.target_dpi / 600)
102
+
103
+ for m in self._gamebox._maps:
104
+ self.fixMap(m)
105
+
106
+ def fixMap(self,map):
107
+ from wand.image import Image, Color
108
+ from wand.drawing import Drawing
109
+
110
+ with VerboseGuard(f'Fixing up map {map._name}') as v:
111
+ tgt = [s for s in self._gamebox._scenarios if s.on_map(map._name)]
112
+ if not tgt:
113
+ v(f'No target scenario found for map')
114
+ return
115
+
116
+ # if len(tgt) > 1:
117
+ # v(f'More than 1 target scenario found for map')
118
+ # return
119
+ dxs = []
120
+ dys = []
121
+ ws = []
122
+ hs = []
123
+ for sc in tgt:
124
+ v(f'Target scenario for map "{map._name}" is "{sc._name}"')
125
+ v(f'Map "{map._name}" image is '
126
+ f'{map._image._image.width}x{map._image._image.height}')
127
+
128
+ ly = sc.map_layout(map._name)[0]
129
+ v(f'Layout at {ly.left},{ly.top} {ly.lrx},{ly.lry}')
130
+
131
+ bb = sc.calc_bounding_box(map._name if self._allmaps
132
+ else None)
133
+ v(f'Bounding box for map: {bb}')
134
+
135
+ ulx = min([s._x - self._gamebox._piece_map[p]._front.width/2
136
+ for s in ly._stacks for p in s._ids]+[0])
137
+ lrx = max([s._x + self._gamebox._piece_map[p]._front.width/2
138
+ for s in ly._stacks for p in s._ids]+
139
+ [map._image._image.width])
140
+ uly = min([s._y - self._gamebox._piece_map[p]._front.height/2
141
+ for s in ly._stacks for p in s._ids]+[0])
142
+ lry = max([s._y + self._gamebox._piece_map[p]._front.height/2
143
+ for s in ly._stacks for p in s._ids]+
144
+ [map._image._image.height])
145
+ v(f'Bounding box for map: {bb} {ulx},{uly},{lrx},{lry}')
146
+
147
+ # Translate the layout
148
+ mar = 10
149
+ sdx = -int(ulx-.5)+mar
150
+ sdy = -int(uly-.5)+mar
151
+ sw = int(lrx+sdx+.5)+2*mar
152
+ sh = int(lry+sdy+.5)+2*mar
153
+ dxs.append(sdx)
154
+ dys.append(sdy)
155
+ ws .append(sw)
156
+ hs .append(sh)
157
+ v(f'{sc._name}: dx={sdx} dy={sdy} w={sw} h={sh}')
158
+
159
+ # Find the smallest translation, and largest size
160
+ dx = max(dxs)
161
+ dy = max(dys)
162
+ w = max(ws)
163
+ h = max(hs)
164
+ v(f'All scenarios translated by {dx},{dy}')
165
+ for sc in tgt:
166
+ sc.translate(dx,dy)
167
+ # v(f'{sc}')
168
+
169
+ v(f'Background image {w}x{h}')
170
+ bg = Image(width=w,height=h,
171
+ colorspace='truecolor',
172
+ background=Color('#404040'))
173
+ bg.format = 'png'
174
+ #map._image._image.save(filename=map.filename)
175
+
176
+ v(f'Compose over at {dx},{dy}')
177
+ bg.composite(map._image._image,left=dx,top=dy)
178
+
179
+ # Replace with new image
180
+ #bg.save(filename='bg_'+map.filename)
181
+ map._image._image = bg #.save(filename=map.filename)
182
+
183
+ # ----------------------------------------------------------------
184
+ def saveImages(self,vmod):
185
+ pass
186
+
187
+ # ----------------------------------------------------------------
188
+ def savePNG(self,vmod,filename,img):
189
+ #with VerboseGuard(f'Saving PNG {filename}'):
190
+ from io import BytesIO
191
+
192
+ stream = BytesIO()
193
+ img.save(stream)
194
+ vmod.addFile('images/'+filename,stream.getvalue())
195
+
196
+ # ----------------------------------------------------------------
197
+ def addDocumentation(self):
198
+ '''Add documentation to the module. This includes rules,
199
+ key-bindings, and about elements.
200
+ '''
201
+ with VerboseGuard('Adding documentation') as v:
202
+ doc = self._game.addDocumentation()
203
+
204
+ desc = f'''<html><body>
205
+ <h1>Module</h1>
206
+ <p>
207
+ This module was created from a ZunTzu gamebox
208
+ {self._gamebox._name} by the Python script
209
+ <code>ztexport.py</code> available from
210
+ </p>
211
+ <p>
212
+ Ctrl-click to select terrain pieces.
213
+ </p>
214
+ <pre>
215
+ https://gitlab.com/wargames_tex/pywargames
216
+ </pre>
217
+ <h1>Game</h1>
218
+ <p>{self._gamebox._desc}</p>
219
+ <h1>Scenarios</h1>
220
+ <ul>
221
+ {'\n'.join(['<li>'+s._name+': '+s._desc+'</li>'
222
+ for s in self._gamebox._scenarios])}
223
+ </ul>
224
+ <h1>Copyright</h1>
225
+ <p>&copy; {self._gamebox._copy}</p>
226
+ </body></html>;'''
227
+
228
+ self._vmod.addFile('help/description.html',desc)
229
+ doc.addHelpFile(title='Description',
230
+ fileName='help/description.html')
231
+
232
+ if self._gamebox._splash:
233
+ v(f'Adding splash screen')
234
+ fname = 'splash.'+self._gamebox._splash.format.lower()
235
+ self.savePNG(self._vmod,fname,self._gamebox._splash)
236
+ self._gamebox._splash = None
237
+ doc.addAboutScreen(title=self._gamebox._name,
238
+ fileName=fname)
239
+
240
+
241
+
242
+ # ----------------------------------------------------------------
243
+ def addDiceHands(self):
244
+ with VerboseGuard(f'Adding dice hands: '
245
+ f'{self._gamebox._dice_hands}') as v:
246
+ seen = set()
247
+ for no,hand in enumerate(self._gamebox._dice_hands):
248
+ name = f'{len(hand._dice)}d{hand._type}'
249
+ if name in seen:
250
+ name += f'_{no}'
251
+
252
+ seen.add(name)
253
+ # v(f'Prepare to add dice {name}')
254
+ self.addDiceHand(name,hand)
255
+
256
+ # ----------------------------------------------------------------
257
+ def addDiceHand(self,name,hand):
258
+ with VerboseGuard(f'Adding dice hand {name} '
259
+ f'{len(hand._dice)}d{hand._type}') as v:
260
+
261
+ cnt = len(hand._dice)
262
+ sid = hand._type
263
+
264
+ rep = '$name$ = '
265
+ if cnt > 1:
266
+ rep += ' + '.join([f'$result{i+1}$' for i in range(cnt)])+' = '
267
+ rep += '$result$'
268
+ ky = {4: '4',
269
+ 6: '6',
270
+ 8: '8',
271
+ 10: '0',
272
+ 12: '2',
273
+ 20: '3'}[hand._type]
274
+ #v(f'Dice defined: {hand._dice}')
275
+ if not self._symbolic_dice:
276
+ self._game.addDiceButton(name = name,
277
+ hotkey = key(ky,ALT),
278
+ tooltip = f'Roll {cnt}d{sid}',
279
+ text = name,
280
+ nDice = cnt,
281
+ nSides = sid,
282
+ reportFormat = rep,
283
+ reportTotal = True)
284
+ return
285
+
286
+
287
+ # Could be used instead if we knew we had images
288
+ #
289
+ v(f'Adding symbolic dice')
290
+
291
+ # Make icon. A nicer way would be to combine the images
292
+ # created below and then scale to the appropriate height.
293
+ #
294
+ # For another time.
295
+ # drawer = DiceDrawer(hand._type,20,20,
296
+ # fg='white',
297
+ # bg='black')
298
+ # image = drawer.draw(hand._type//2)
299
+ # image.format = 'png'
300
+ # fn = f'{name}-logo.png'
301
+ # self.savePNG(self._vmod,fn,image)
302
+
303
+ rep = '{name+": "+'
304
+ def img(i,d):
305
+ return (f'"<img src=\'{name}{i}_"+result{i}+".png\' '
306
+ f'width=\'24\' height=\'24\'>"')
307
+ def txt(i,d):
308
+ bg = d['pips']
309
+ fg = d['color']
310
+ bg = 0xFFFFFF
311
+ if fg == 0xFFFFFF:
312
+ fg = 0
313
+
314
+ return (f'"<span style=\'color:#{fg:06x}; '
315
+ f'background-color:#{bg:06x}; '
316
+ f'font-weight:bold; '
317
+ f'padding: 5px;\'>"+'
318
+ f'result{i}+"</span>"')
319
+
320
+ rep += '+'.join([img(i+1,d)
321
+ for i,d in enumerate(hand._dice)])+\
322
+ '+" = "+'
323
+ rep += '+" + "+'.join([txt(i+1,d)
324
+ for i,d in enumerate(hand._dice)])
325
+ if cnt > 1:
326
+ rep += '+" = <b>"+numericalTotal+"</b>"}'
327
+ else:
328
+ rep += '}'
329
+
330
+
331
+ diceW = 80
332
+ diceH = 80
333
+ iconfn = f'{name}-icon.png'
334
+ but = self._game.addSymbolicDice(
335
+ name = name,
336
+ icon = iconfn,
337
+ hotkey = key(str(hand._type),ALT),
338
+ tooltip = f'Roll {cnt}d{sid}',
339
+ text = name,
340
+ doReport = True,
341
+ resultWindow = True,
342
+ format = rep,
343
+ windowX = (diceW+10)*len(hand._dice),
344
+ windowY = diceH+10)
345
+ v(f'Report format: {rep}')
346
+
347
+ from wand.image import Image as WImage
348
+ from wand.color import Color
349
+ from random import randint
350
+
351
+ icon = WImage(format='png',width=diceW,height=diceH)
352
+ icon.alpha_channel = True
353
+ icon.background_color = Color('transparent')
354
+
355
+ for i, dice in enumerate(hand._dice):
356
+ # v(f'- {dice}')
357
+ dien = f'{name}{i+1}'
358
+ die = but.addDie(name=dien)
359
+ drawer = DiceDrawer(hand._type,80,80,
360
+ fg=dice['pips'],
361
+ bg=dice['color'])
362
+
363
+ imgs = []
364
+ for num in range(1,hand._type+1):
365
+ if hand._type == 10 and num == 10:
366
+ num = 0
367
+ image = drawer.draw(num)
368
+ image.format = 'png'
369
+ imgs.append(image)
370
+ fn = f'{dien}_{num}.png'
371
+ self.savePNG(self._vmod,fn,image)
372
+
373
+ die.addFace(icon = fn,
374
+ text = f'{num}',
375
+ value = num)
376
+
377
+ icon.sequence.append(imgs[randint(0,hand._type-1)])
378
+
379
+ icon.smush(stacked=False,offset=0)
380
+ sc = 20 / diceH
381
+ icon.resize(int(sc * icon.width),int(sc * icon.height))
382
+ icon.format = 'png'
383
+ self.savePNG(self._vmod,iconfn,icon)
384
+
385
+
386
+
387
+ # ----------------------------------------------------------------
388
+ def addPrototypes(self):
389
+ with VerboseGuard(f'Creating prototypes'):
390
+ protos = self._game.addPrototypes()
391
+
392
+ traits = [TrailTrait(),
393
+ RotateTrait(nangles = self._n_rotations),
394
+ DeleteTrait(),
395
+ BasicTrait()]
396
+ protos.addPrototype(name = 'Basic prototype',
397
+ description = 'Prototype for most',
398
+ traits = traits)
399
+
400
+
401
+ traits = [PrototypeTrait(name = 'Basic prototype'),
402
+ NoStackTrait(
403
+ select = NoStackTrait.CTRL_SELECT,
404
+ bandSelect = NoStackTrait.NEVER_BAND_SELECT,
405
+ move = NoStackTrait.SELECT_MOVE,
406
+ canStack = False,
407
+ description = 'Terrain does not stack'),
408
+ MarkTrait(name = 'PieceLayer',
409
+ value = 'Terrains'),
410
+ BasicTrait()]
411
+ protos.addPrototype(name = 'Terrain prototype',
412
+ description = 'Prototype for terrain',
413
+ traits = traits)
414
+
415
+ traits = [PrototypeTrait(name = 'Basic prototype'),
416
+ MarkTrait(name = 'PieceLayer',
417
+ value = 'Pieces'),
418
+ BasicTrait()]
419
+
420
+ protos.addPrototype(name = 'Piece prototype',
421
+ description = 'Prototype for pieces',
422
+ traits = traits)
423
+
424
+ traits = [PrototypeTrait(name = 'Basic prototype'),
425
+ MarkTrait(name = 'PieceLayer',
426
+ value = 'Cards'),
427
+ BasicTrait()]
428
+
429
+ protos.addPrototype(name = 'Card prototype',
430
+ description = 'Prototype for cards',
431
+ traits = traits)
432
+
433
+ # ----------------------------------------------------------------
434
+ def addPieces(self):
435
+ with VerboseGuard(f'Adding pieces') as v:
436
+ window = self._game.addPieceWindow(name = 'Pieces',
437
+ hotkey = key('C',ALT))
438
+
439
+ combo = window.addCombo(entryName = 'Pieces')
440
+
441
+ self._piece_slots = {}
442
+
443
+ for sheet in self._gamebox._counter_sheets:
444
+ v(sheet._name)
445
+ if len(sheet._piece) <= 0 and len(sheet._card) <= 0:
446
+ continue
447
+ plist = self.addPieceContainer(combo, sheet._name)
448
+ self.addSheetPieces(plist,sheet._piece,sheet._card)
449
+
450
+ # ----------------------------------------------------------------
451
+ def addSheetPieces(self,container,pieces,cards):
452
+ with VerboseGuard(f'Adding pieces to {container["entryName"]} '+
453
+ f'{len(pieces)} pieces, {len(cards)} cards'):
454
+ for piece in pieces+cards:
455
+ boardName = container['entryName']
456
+ board = self._game.getBoards(asdict=True).get(boardName,None)
457
+ mapName = board.getMap()['mapName'] if board else ''
458
+ no = self._gamebox._piece_id[piece]
459
+ traits = [
460
+ PrototypeTrait(
461
+ name = (('Terrain' if piece._terrain else
462
+ ('Card' if piece._card else 'Piece'))+
463
+ ' prototype')),
464
+ SendtoTrait(
465
+ mapName = boardName,
466
+ boardName = mapName,
467
+ name = f'Return to {boardName}',
468
+ key = key('R',CTRL_SHIFT),
469
+ restoreName = '',
470
+ restoreKey = '',
471
+ x = piece._x,
472
+ y = piece._y,
473
+ description = 'Return to where it came from',
474
+ destination = SendtoTrait.LOCATION)
475
+ ]
476
+ name = f'{no:06d}'
477
+ front_name = f'{name}_front.{piece._front.format.lower()}'
478
+ back_name = (f'{name}_back.{piece._back.format.lower()}'
479
+ if piece.two_sides else None)
480
+ if back_name is not None:
481
+ traits.extend([
482
+ LayerTrait(
483
+ images = [front_name,back_name],
484
+ newNames = ['','+ reversed'],
485
+ activateName = '',
486
+ decreaseName = '',
487
+ increaseName = 'Flip',
488
+ increaseKey = key('F'),
489
+ decreaseKey = '',
490
+ name = 'Step'),
491
+ ReportTrait(key('F'))])
492
+ gpid = self._game.nextPieceSlotId()
493
+ size = piece.size
494
+ traits.append(
495
+ BasicTrait(name = name,
496
+ filename = front_name,
497
+ gpid = gpid))
498
+
499
+ self._piece_slots[piece] = container.addPieceSlot(
500
+ entryName = name,
501
+ gpid = gpid,
502
+ traits = traits,
503
+ width = piece.width,
504
+ height = piece.height)
505
+
506
+ for img,fname in zip([piece._front,piece._back],
507
+ [front_name,back_name]):
508
+ if img is None:
509
+ continue
510
+ fnameext = fname
511
+ self.savePNG(self._vmod,fnameext,img)
512
+ #piece._front = None
513
+ #piece._back = None
514
+
515
+ # self.saveImages(self,vmod)
516
+ # ----------------------------------------------------------------
517
+ def addPieceContainer(self,container,name):
518
+ #panel = container.addPanel(entryName = name,
519
+ # fixed = False,
520
+ # vert = True)
521
+ #return panel
522
+ panel = container.addPanel(entryName = name, fixed = False)
523
+ plist = panel.addList(entryName = f'{name}',
524
+ width = 300,
525
+ height = 300,
526
+ divider = 150)
527
+ return plist
528
+
529
+ # ----------------------------------------------------------------
530
+ def addMapLayers(self,map):
531
+ layerDesc = {'Terrains': {'t': 'Terrain', 'i': ''},
532
+ 'Cards': {'t': 'Cards', 'i': ''},
533
+ 'Pieces': {'t': 'Pieces', 'i': ''}}
534
+ layers = map.addLayers(property = 'PieceLayer',
535
+ layerOrder = layerDesc)
536
+ for layerTitle, layerData in layerDesc.items():
537
+ layers.addControl(name = layerTitle,
538
+ tooltip = f'Toggle display of {layerTitle}',
539
+ text = f'Toggle {layerData["t"]}',
540
+ icon = layerData['i'],
541
+ layers = [layerTitle])
542
+ layers.addControl(name = 'Show all',
543
+ tooltip = 'Show all',
544
+ text = 'Show all',
545
+ command = LayerControl.ENABLE,
546
+ layers = list(layerDesc.keys()))
547
+ map.addMenu(description = 'Toggle layers',
548
+ text = '',
549
+ tooltip = 'Toggle display of layers',
550
+ icon = '/images/inventory.gif',
551
+ menuItems = ([f'Toggle {ln["t"]}'
552
+ for ln in layerDesc.values()]
553
+ +['Show all']))
554
+
555
+ # ----------------------------------------------------------------
556
+ def addMapDefaults(self,map):
557
+ s = ZTImage.target_dpi / 150
558
+ z = [0.05,0.1,0.2,0.25,0.333,0.4,0.5,
559
+ 0.555,0.625,0.75,1.0,1.25,1.6,1.8,2]
560
+ if s > 4: z = z[:-3]
561
+ if s < 2: z = z[2:]
562
+ z0 = 6
563
+ map.addGlobalMap()
564
+ # Basics
565
+ map.addStackMetrics(
566
+ exSepX = int(s * 12 + .5),
567
+ exSepY = int(s * 16 + .5),
568
+ unexSepX = int(s * 8 + .5),
569
+ unexSepY = int(s * 10 + .5)
570
+ )
571
+ map.addImageSaver()
572
+ map.addTextSaver()
573
+ map.addForwardToChatter()
574
+ map.addMenuDisplayer()
575
+ map.addMapCenterer()
576
+ map.addStackExpander()
577
+ map.addPieceMover()
578
+ map.addKeyBufferer()
579
+ map.addForwardKeys()
580
+ map.addSelectionHighlighters()
581
+ map.addHighlightLastMoved()
582
+ map.addScroller()
583
+ map.addZoomer(zoomLevels = z, zoomStart = z0)
584
+
585
+ # ----------------------------------------------------------------
586
+ def addMaps(self):
587
+ with VerboseGuard(f'Adding maps') as v:
588
+ if not self._allmaps:
589
+ self.addMap(*self._gamebox._maps)
590
+ return
591
+
592
+ mapNames = [self.addMap(m) for m in self._gamebox._maps]
593
+
594
+ filtered = [n for n in mapNames if n != '' and n != self._main_map]
595
+ v(f'Filtered set of maps: {filtered}')
596
+ if len(filtered) > 0:
597
+ self._game.addMenu(description = 'Maps',
598
+ text = 'Maps',
599
+ tooltip = 'Show or hide maps',
600
+ icon = '/images/map.gif',
601
+ menuItems = filtered)
602
+
603
+
604
+ # ----------------------------------------------------------------
605
+ def addMap(self,*maps):
606
+ with VerboseGuard(f'Adding maps') as v:
607
+ isMain = False
608
+ mapName = maps[0]._name
609
+ if not self._main_map or self._main_map == mapName:
610
+ self._main_map = mapName
611
+ isMain = True
612
+
613
+ map = self._game.addMap(mapName = mapName,
614
+ buttonName = '' if isMain else mapName,
615
+ hotkey = '' if isMain else key('M',ALT),
616
+ launch = not isMain
617
+ )
618
+ self.addMapDefaults(map)
619
+ map.addLOS()
620
+ map.addCounterDetailViewer()
621
+ self.addMapLayers(map)
622
+
623
+ picker = map.addBoardPicker()
624
+ self._picker = picker
625
+ for m in maps:
626
+ imgname = m.filename
627
+ size = m.size
628
+ brd = picker.addBoard(name = m._name,
629
+ image = imgname,
630
+ width = size[0],
631
+ height = size[1])
632
+ zoned = brd.addZonedGrid()
633
+ zoned.addHighlighter()
634
+ zone = zoned.addZone(name = 'Full',
635
+ useParentGrid = False,
636
+ path=(f'{0},{0};' +
637
+ f'{size[0]},{0};' +
638
+ f'{size[0]},{size[1]};' +
639
+ f'{0},{size[1]}'))
640
+
641
+ self.savePNG(self._vmod,imgname,m._image._image)
642
+ m._image._image = None
643
+
644
+ return mapName
645
+
646
+ # ----------------------------------------------------------------
647
+ def addCounterSheets(self):
648
+ with VerboseGuard(f'Adding Charts') as v:
649
+ window = \
650
+ self._game.addChartWindow(name='Charts',
651
+ hotkey = key('A',ALT),
652
+ description = 'Charts',
653
+ text = '',
654
+ icon = '/images/chart.gif',
655
+ tooltip = 'Show/hide Charts')
656
+ tabs = window.addTabs(entryName='Charts')
657
+
658
+ for sheet in self._gamebox._counter_sheets:
659
+ widget = tabs.addMapWidget(entryName=sheet._name)
660
+ self.addCounterSheet(widget,sheet)
661
+
662
+ # ----------------------------------------------------------------
663
+ def addCounterSheet(self,widget,sheet):
664
+ with VerboseGuard(f'Adding counter sheet {sheet._name}') as v:
665
+ map = widget.addWidgetMap(mapName = sheet._name,
666
+ markMoved = 'Never',
667
+ hotkey = '')
668
+ self.addMapDefaults(map)
669
+ self.addMapLayers(map)
670
+
671
+ size = sheet.size
672
+ picker = map.addPicker()
673
+ brd = picker.addBoard(name = sheet._name,
674
+ image = sheet.filename)
675
+ zoned = brd.addZonedGrid()
676
+ zoned.addHighlighter()
677
+ zone = zoned.addZone(name = 'Full',
678
+ useParentGrid = False,
679
+ path=(f'{0},{0};' +
680
+ f'{size[0]},{0};' +
681
+ f'{size[0]},{size[1]};' +
682
+ f'{0},{size[1]}'))
683
+
684
+ self.savePNG(self._vmod,sheet.filename,sheet._front._image)
685
+ self.addAtStart(map,sheet._piece+sheet._card)
686
+ sheet._front._image = None
687
+
688
+ # ----------------------------------------------------------------
689
+ def addAtStart(self,map,pieces):
690
+ with VerboseGuard(f'Adding at-start to {map["mapName"]} '+
691
+ f'{len(pieces)} pieces and cards') as g:
692
+ toAdd = {}
693
+
694
+ for piece in pieces:
695
+ x = piece._x
696
+ y = piece._y
697
+ grid = (x,y)
698
+ slot = self._piece_slots[piece]
699
+ #g(f'Adding piece at {x},{y}')
700
+ if grid not in toAdd:
701
+ toAdd[grid] = {'center': (x,y),
702
+ 'pieces': [] }
703
+ toAdd[grid]['pieces'].append(slot)
704
+
705
+ for grid, dpieces in toAdd.items():
706
+ center = dpieces['center']
707
+ name = f'{grid[0]:06d}{grid[1]:06d}'
708
+ atstart = map.addAtStart(name = name,
709
+ useGridLocation = False,
710
+ owningBoard = map["mapName"],
711
+ x = center[0],
712
+ y = center[1])
713
+ atstart.addPieces(*dpieces['pieces'])
714
+
715
+ # ----------------------------------------------------------------
716
+ def addDeadMap(self,sides):
717
+ pass
718
+
719
+ # ----------------------------------------------------------------
720
+ def addScenarios(self):
721
+ menu = self._game.addPredefinedSetup(name = 'Setups',
722
+ isMenu = True)
723
+
724
+ menu.addPredefinedSetup(name = 'No setup',
725
+ file = '',
726
+ useFile = False,
727
+ description = 'No initial setup')
728
+
729
+ for sc in self._gamebox._scenarios:
730
+ self.addScenario(sc,menu)
731
+
732
+ # ----------------------------------------------------------------
733
+ def remapIds(self,scenario):
734
+ from pprint import pprint
735
+ with VerboseGuard(f'Remapping piece ID for scenario '
736
+ f'"{scenario._name}"') as v:
737
+ tmp = []
738
+ for layout in scenario._layouts:
739
+ cs = self._gamebox._sheets_map.get(layout._name,None)
740
+ if not cs:
741
+ v(f'Missing counter sheet {layout._name} - maybe map')
742
+ continue
743
+
744
+ tmp.extend(cs._piece+cs._card)
745
+
746
+
747
+ # Map from ID to piece - only used counter sheets in this scenario
748
+ # pprint(tmp)
749
+ piece_map = {i: p for i,p in enumerate(tmp)}
750
+ # print(piece_map)
751
+ # piece_id = {v: k for k,v in piece_map.items()}
752
+
753
+ return piece_map
754
+
755
+ # ----------------------------------------------------------------
756
+ def changeSlot(self,slot,flipped,angle,changed):
757
+ if angle == 0 and not flipped:
758
+ return
759
+
760
+ # Valid angles are
761
+ #
762
+ # for i in range(nangles):
763
+ # angle[i] = - i * 360 // nangles
764
+ #
765
+ # validAngles = [-i * 360 // nangles for i in range(nangles)]
766
+ changed[slot['entryName']] = {'angle':angle,
767
+ 'step': 2 if flipped else 1}
768
+
769
+ # ----------------------------------------------------------------
770
+ def updateSlot(self,name,traits,changed):
771
+ changes = changed.get(name,None)
772
+ if not changes:
773
+ return
774
+
775
+ with VerboseGuard(f'Updating rotation and step state of '
776
+ f'{name} to {changes}') as v:
777
+ rot = Trait.findTrait(traits, RotateTrait.ID)
778
+ lyr = Trait.findTrait(traits, LayerTrait.ID,
779
+ key='name', value='Step')
780
+ if not rot and not lyr:
781
+ v(f'No traits to modify!')
782
+ return
783
+
784
+ angle = changes['angle']
785
+ step = changes['step']
786
+ nangle = int(rot['nangles'])
787
+ fangle = angle
788
+ if nangle != 1:
789
+ # Funny calculation in VASSAL
790
+ a = ((angle % 360) - 360) % 360
791
+ e = (-a / 360) * nangle
792
+ fangle = int(round(e) % nangle)
793
+ # v(f'nangle={nangle} fangle={fangle}')
794
+ if rot: rot.setState(angle = fangle)
795
+ if lyr: lyr.setState(level = step)
796
+
797
+
798
+ # ----------------------------------------------------------------
799
+ def restoreSlot(self,slot,step,angle):
800
+ # traits = slot.getTraits()
801
+ # rot = Trait.findTrait(traits, RotateTrait.ID)
802
+ # lyr = Trait.findTrait(traits, LayerTrait.ID,
803
+ # key='name', value='Step')
804
+ #
805
+ # rot.setState(angle=rot)
806
+ # lyr.setState(level=step)
807
+ #
808
+ # slot.setTraits(*traits)
809
+ pass
810
+
811
+ # ----------------------------------------------------------------
812
+ def addScenario(self,scenario,menu):
813
+ from sys import stderr
814
+ from pprint import pprint
815
+
816
+ with VerboseGuard(f'Adding a scenario {scenario._name}') as v:
817
+ vsav = VSav(self._build,self._vmod)
818
+ save = vsav.addSaveFile()
819
+ changed = {}
820
+
821
+ # ZunTzu reads in the counter sheets specified in a given
822
+ # scenario, and then builds and array of those counters.
823
+ # However, since we already have read in the counters, we
824
+ # need to simply remap the index here.
825
+ piece_map = self.remapIds(scenario)#self._gamebox._piece_map
826
+ # pprint(piece_map)
827
+
828
+ selectedBoard = None
829
+ selectedMap = None
830
+ # First, loop over layouts and see if we can find the map
831
+ # we're looking for.
832
+ for layout in scenario._layouts:
833
+ boardName = layout._name
834
+ board = self._game.getBoards(asdict=True).get(boardName,
835
+ None)
836
+ if not board:
837
+ # Try the next layout
838
+ continue
839
+
840
+ map = board.getMap()
841
+ if not map:
842
+ # A board without a map?
843
+ continue
844
+
845
+ if not isinstance(map.getParent(),Game):
846
+ # Parent of map is not the game
847
+ continue
848
+
849
+ selectedMap = map
850
+ selectedBoard = board
851
+ break
852
+
853
+ if not selectedMap:
854
+ print(f'No map found for scenario {scenario._name}')
855
+
856
+ mapName = selectedMap['mapName']
857
+ boardName = selectedBoard['name']
858
+
859
+ for layout in scenario._layouts:
860
+ v(f'Layout {layout._name} with {len(layout._stacks)}')
861
+ if len(layout._stacks) <= 0:
862
+ continue
863
+
864
+ thisMap = mapName
865
+ if self._allmaps:
866
+ boardName = layout._name
867
+ board = self._game.getBoards(asdict=True)\
868
+ .get(boardName, None)
869
+
870
+ map = board.getMap() if board else None
871
+ if map:
872
+ # A board without a map?
873
+ thisMap = map['mapName']
874
+
875
+ # In principle, it could be done like this, but to be
876
+ # on the safe side, we do it a tad more laboriously.
877
+ #
878
+ # mapped = {(s._x,s._y):
879
+ # [self._piece_slots[self._gamebox._piece_map[i]]
880
+ # for i in s._ids]
881
+ # for s in layout._stacks}
882
+ mapped = {}
883
+ v(f'Layout has {len(layout._stacks)}')
884
+ for s in layout._stacks:
885
+ slots = []
886
+ for i,f,r in zip(s._ids,s._flip,s._rot):
887
+ v(f'Piece {i}')
888
+ piece = piece_map.get(i, None)
889
+ if not piece:
890
+ print(f'Could not find piece for id {i}',
891
+ file=stderr)
892
+ continue
893
+ slot = self._piece_slots.get(piece, None)
894
+ if not slot:
895
+ print(f'Could not find slot for piece {piece}',
896
+ file=stderr)
897
+ continue
898
+
899
+ # Set states
900
+ self.changeSlot(slot, f, r, changed)
901
+
902
+ # v(f'id={i} -> piece={piece} -> slot={slot}')
903
+ slots.append(slot)
904
+
905
+ mapped[(int(s._x+.5),int(s._y+.5))] = slots
906
+
907
+ # self._picker.getMap()['mapName']
908
+ v(f'Add pieces to map: {thisMap}')
909
+ save.addNoGrid(thisMap, mapped)
910
+
911
+ v(f'Selected map/board {mapName}/{boardName}')
912
+
913
+ picker = selectedMap.getBoardPicker(single=True)[0]
914
+
915
+ if picker:
916
+ picker.selectBoard(boardName)
917
+ v(f'Selected map: {picker["selected"]}')
918
+
919
+ # print('\n'.join(save.getLines()))
920
+ # lines = []
921
+ # save._otherLines(lines)
922
+ # print('\n'.join(lines))
923
+ vsav.run(savename = scenario._file+'.vsav',
924
+ description = scenario._desc,
925
+ update = lambda n,t : self.updateSlot(n,t,changed))
926
+
927
+ if picker:
928
+ picker.selectBoard(None)
929
+
930
+ menu.addPredefinedSetup(name=scenario._name,
931
+ useFile=True,
932
+ file=scenario._file+'.vsav',
933
+ description=scenario._desc)
934
+ #
935
+ # EOF
936
+ #