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,495 @@
1
+ ## BEGIN_IMPORT
2
+ from . moduledata import ModuleData
3
+ from . withtraits import PieceSlot, DummyWithTraits
4
+ from . trait import Trait
5
+ from . traits import BasicTrait, StackTrait
6
+ from . element import DummyElement
7
+ from . mapelements import AtStart
8
+ ## END_IMPORT
9
+
10
+ # ====================================================================
11
+ class SaveIO:
12
+ '''Wrapper around a save file
13
+
14
+ Save file is
15
+
16
+ "!VCSK" KEY content
17
+
18
+ Key is two bytes drawn as a random number in 0-255. Content is
19
+ two bytes per character. Content characters are encoded with the
20
+ random key.
21
+
22
+ Save file (.vsav) content is
23
+
24
+ "begin_save" ESC
25
+ "\" ESC
26
+ [commands]* ESC
27
+ "PLAYER" name password side ESC
28
+ [map+"BoardPicker" name x y ESC]+
29
+ "SETUP_STACK" ESC
30
+ "TURN"+name state ESC
31
+ "end_save"
32
+
33
+ Commands are
34
+
35
+ "+/" id "/" body "\"
36
+
37
+ where body are
38
+
39
+ "stack" "/" mapName ; x ; y ; ids "\"
40
+ piece_type "/" piece_state (x and y set here too) "\"
41
+
42
+ x and y are pixel coordinates (sigh!). This means we have to know
43
+
44
+ - the pixel location of a hex
45
+ - the hex coordinates of that hex
46
+ - whether rows and columns are descending
47
+ - if even hexes are higher or not
48
+
49
+ The two first items _must_ be user supplied (I think). When we
50
+ add stacks or pieces, we must then get the numerical hex
51
+ coordinates - not what the user specified in the VASSAL editor or
52
+ the like. Of course, this means opening the module before writing
53
+ the patch.py script.
54
+
55
+ It seems like every piece is added in a stack.
56
+
57
+ The id is a numerical value. Rather big (e.g., 1268518000806). It
58
+ is the current number of miliseconds since epoch, with offset to
59
+ disambiguate.
60
+
61
+ The ID is the current time, taken from a milisecond clock,
62
+ possibly adjusted up if there is a clash. This is all managed by
63
+ the GameState class.
64
+
65
+ '''
66
+ VCS_HEADER = b'!VCSK'
67
+ VK_ESC = chr(27)
68
+ DEC_MAP = {
69
+ # 0-9
70
+ 0x30: 0x30,
71
+ 0x31: 0x30,
72
+ 0x32: 0x30,
73
+ 0x33: 0x30,
74
+ 0x34: 0x30,
75
+ 0x35: 0x30,
76
+ 0x36: 0x30,
77
+ 0x37: 0x30,
78
+ 0x38: 0x30,
79
+ 0x39: 0x30,
80
+ # A-F
81
+ 0x41: 0x37,
82
+ 0x42: 0x37,
83
+ 0x43: 0x37,
84
+ 0x44: 0x37,
85
+ 0x45: 0x37,
86
+ 0x46: 0x37,
87
+ # a-f
88
+ 0x61: 0x57,
89
+ 0x62: 0x57,
90
+ 0x63: 0x57,
91
+ 0x64: 0x57,
92
+ 0x65: 0x57,
93
+ 0x66: 0x57
94
+ }
95
+ ENC_MAP = [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9',
96
+ b'a',b'b',b'c',b'd',b'e',b'f']
97
+
98
+ # ----------------------------------------------------------------
99
+ @classmethod
100
+ def decHex(cls,b):
101
+ '''Decode a single char into a number
102
+
103
+ If the encoded number is b, then the decoded number is
104
+
105
+ b - off
106
+
107
+ where off is an offset that depends on b
108
+
109
+ off = 0x30 if 0x30 <= b <= 0x39
110
+ 0x37 if 0x41 <= b <= 0x46
111
+ 0x57 if 0x61 <= b <= 0x66
112
+ '''
113
+ return b - cls.DEC_MAP[b]
114
+ # --------------------------------------------------------------------
115
+ @classmethod
116
+ def readByte(cls,inp,key):
117
+ '''Read a single byte of information from input stream
118
+
119
+ Two characters (c1 and c2) are read from input stream, and the
120
+ decoded byte is then
121
+
122
+ ((dechex(c1) << 4 | dechex(c2)) ^ key) & 0xFF
123
+
124
+ Parameters
125
+ ----------
126
+ inp : stream
127
+ Input to read from
128
+ key : int
129
+ Key to decode the input
130
+
131
+ Returns
132
+ -------
133
+ b : int
134
+ The read byte
135
+ '''
136
+ try:
137
+ pair = inp.read(2)
138
+ except Exception as e:
139
+ from sys import stderr
140
+ print(e,file=stderr)
141
+ return None
142
+
143
+ if len(pair) < 2:
144
+ return None
145
+
146
+ return ((cls.decHex(pair[0]) << 4 | cls.decHex(pair[1])) ^ key) & 0xFF
147
+ # --------------------------------------------------------------------
148
+ @classmethod
149
+ def readSave(cls,file,alsometa=False):
150
+ '''Read data from save file. The data is read into lines
151
+ returned as a list.
152
+
153
+ '''
154
+ from zipfile import ZipFile
155
+
156
+ # We open the save file as a zip file
157
+ with ZipFile(file,'r') as z:
158
+ # open the save file in the archive
159
+ save = z.open('savedGame','r')
160
+
161
+ # First, we check the header
162
+ head = save.read(len(cls.VCS_HEADER))
163
+ assert head == cls.VCS_HEADER, \
164
+ f'Read header {head} is not {cls.VCS_HEADER}'
165
+
166
+ # Then, read the key
167
+ pair = save.read(2)
168
+ key = (cls.decHex(pair[0]) << 4 | cls.decHex(pair[1]))
169
+
170
+ # Now read content, one byte at a time
171
+ content = ''
172
+ while True:
173
+ byte = cls.readByte(save,key)
174
+ if byte is None:
175
+ break
176
+
177
+ # Convert byte to character
178
+ content += chr(byte)
179
+
180
+ lines = content.split(cls.VK_ESC)
181
+
182
+ if alsometa:
183
+ savedata = z.read(VSav.SAVE_DATA)
184
+ moduledata = z.read(VMod.MODULE_DATA)
185
+
186
+ if not alsometa:
187
+ return key, lines
188
+
189
+ return key,lines,savedata,moduledata
190
+
191
+ # --------------------------------------------------------------------
192
+ @classmethod
193
+ def writeByte(cls,out,byte,key):
194
+ '''Write a single byte
195
+
196
+ Parameters
197
+ ----------
198
+ out : IOStream
199
+ Stream to write to
200
+ byte : char
201
+ Single byte to write
202
+ key : int
203
+ Key to encode with (defaults to 0xAA - alternating 0's and 1's)
204
+ '''
205
+ b = ord(byte) ^ key
206
+ pair = cls.ENC_MAP[(b & 0xF0) >> 4], cls.ENC_MAP[b & 0x0F]
207
+ out.write(pair[0])
208
+ out.write(pair[1])
209
+
210
+ # --------------------------------------------------------------------
211
+ @classmethod
212
+ def writeInZip(cls,z,key,lines,filename='savedGame'):
213
+ '''Write a save file in a zip file (VMod)'''
214
+ # open the save file in the archive
215
+ with z.open(filename,'w') as save:
216
+ # Write header
217
+ save.write(cls.VCS_HEADER)
218
+
219
+ # Split key
220
+ pair = cls.ENC_MAP[(key & 0xF0) >> 4], cls.ENC_MAP[(key & 0x0F)]
221
+ save.write(pair[0])
222
+ save.write(pair[1])
223
+
224
+ # Form content
225
+ content = cls.VK_ESC.join(lines)
226
+
227
+ # Write each character as two
228
+ for c in content:
229
+ cls.writeByte(save, c, key)
230
+
231
+ # --------------------------------------------------------------------
232
+ @classmethod
233
+ def writeSave(cls,file,key,lines,savedata=None,moduledata=None):
234
+ '''Write a save file'''
235
+ from zipfile import ZipFile, ZIP_DEFLATED
236
+
237
+ # We open the save file as a zip file
238
+ with ZipFile(file,'w',ZIP_DEFLATED) as z:
239
+ cls.writeInZip(z,key,lines,filename='savedGame')
240
+
241
+ if savedata is not None:
242
+ z.writestr(VSav.SAVE_DATA,savedata)
243
+ z.writestr(VMod.MODULE_DATA,moduledata)
244
+
245
+ # ====================================================================
246
+ #
247
+ # VSave file
248
+ #
249
+ class SaveFile:
250
+ def __init__(self,game,firstid=None):
251
+ '''Creates a save file to add positions to'''
252
+ from time import time
253
+ self._game = game
254
+ self._counters = {}
255
+ self._stacks = {}
256
+ self._pieces = self._game.getPieces(asdict=True)
257
+ self._nextId = (int(time()*1000) - 360000
258
+ if firstid is None else firstid)
259
+
260
+ def add(self,grid,mapname,**kwargs):
261
+ '''Add pieces to the save.
262
+
263
+ Parameters
264
+ ----------
265
+ grid : BaseGrid
266
+ Grid to add pieces to
267
+ kwargs : dict
268
+ Either a map from piece name to hex position,
269
+ Or a map from hex position to list of pieces
270
+ '''
271
+ for k,v in kwargs.items():
272
+ # print('Add to save',k,v)
273
+ self._add(grid,mapname,k,v)
274
+
275
+ def addNoGrid(self,mapName,mapping):
276
+ for k,v in mapping.items():
277
+ # print('Add to save',k,v)
278
+ self._add(None,mapName,k,v)
279
+
280
+
281
+ def _add(self,grid,mapName,k,v):
282
+
283
+ '''Add to the save'''
284
+ with VerboseGuard(f'Adding piece(s) to save: {len(k)}') as vg:
285
+ # print(f'Adding {k} -> {v}')
286
+ loc = None
287
+ piece = self._pieces.get(k,None)
288
+ pieces = []
289
+ boardName = (grid.getMap()['mapName']
290
+ if mapName is None else mapName)
291
+ # print(f'Map name: {mapName}')
292
+ vg(f'Adding to {boardName}')
293
+ if piece is not None:
294
+ vg(f'Key {k} is a piece')
295
+ #print(f'Key is piece: {k}->{piece}')
296
+ pieces.append(piece)
297
+ loc = v
298
+ else:
299
+ vg(f'Key {k} is a location')
300
+ # Key is not a piece name, so a location
301
+ loc = k
302
+ # Convert value to iterable
303
+ try:
304
+ iter(v)
305
+ except:
306
+ v = list(v)
307
+
308
+ for vv in v:
309
+ if isinstance(vv,PieceSlot):
310
+ pieces.append(vv)
311
+ continue
312
+ if isinstance(vv,str):
313
+ piece = self._pieces.get(vv,None)
314
+ if piece is None:
315
+ continue
316
+ pieces.append(piece)
317
+
318
+ vg(f'Loc: {loc} -> {pieces}')
319
+ if len(pieces) < 1:
320
+ return
321
+
322
+ if (mapName,loc) not in self._stacks:
323
+ vg(f'Adding stack {mapName},{loc}')
324
+ coord = grid.getLocation(loc) if grid is not None else loc
325
+ if coord is None:
326
+ print(f'did not get coordinates from {loc}')
327
+ return
328
+ self._stacks[(mapName,loc)] = {
329
+ 'x': coord[0],
330
+ 'y': coord[1],
331
+ 'pids': [] }
332
+
333
+ place = self._stacks[(mapName,loc)]
334
+ for piece in pieces:
335
+ name = piece['entryName']
336
+ gpid = piece['gpid']
337
+ counter = self._counters.get((name,gpid),None)
338
+ vg(f'Got counter {counter} for {name},{gpid}')
339
+
340
+ if counter is None:
341
+ if gpid == 0:
342
+ print(f'making new counter with pid={self._nextId}: '
343
+ f'{gpid}')
344
+ gpid = self._nextId
345
+ self._nextId += 1
346
+
347
+
348
+ vg(f'Save adding counter with pid={gpid}')
349
+ counter = {'pid': gpid,
350
+ 'piece': piece,
351
+ 'board': mapName,
352
+ 'x': place['x'],
353
+ 'y': place['y'],
354
+ }
355
+ self._counters[(name,gpid)] = counter
356
+
357
+ vg(f'Adding to stack {mapName},{loc}: {counter}')
358
+ place['pids'].append(counter['pid'])
359
+
360
+ def getLines(self,update=None):
361
+ '''Get the final lines of code'''
362
+ key = 0xAA # fixed key
363
+
364
+ lines = ['begin_save',
365
+ '',
366
+ '\\']
367
+
368
+ self._pieceLines(lines,update=update)
369
+ self._otherLines(lines)
370
+
371
+ lines.append('end_save')
372
+ return lines
373
+
374
+ def _pieceLines(self,lines,update=lambda t:t):
375
+ '''Add piece lines to save file
376
+
377
+ Parameters
378
+ ----------
379
+ lines : list
380
+ The lines to add
381
+ '''
382
+ # print(self._counters)
383
+ for (name,gpid),counter in self._counters.items():
384
+ iden = counter['pid']
385
+ piece = counter['piece']
386
+ traits = piece.getTraits()
387
+ traits = Trait.flatten(traits,self._game)
388
+ # Get last - trait (basic piece), and modify coords
389
+ basic = traits[-1]
390
+ basic['map'] = counter['board']
391
+ basic['x'] = counter['x']
392
+ basic['y'] = counter['y']
393
+ # Set old location if possible
394
+ parent = piece.getParent(DummyElement,checkTag=False)
395
+ if parent is not None and parent._node.nodeName == AtStart.TAG:
396
+ oldLoc = parent['location']
397
+ oldBoard = parent['owningBoard']
398
+ oldMap = self._game.getBoards()[oldBoard].getMap()['mapName']
399
+ oldX = parent['x']
400
+ oldY = parent['y']
401
+ oldZone = None
402
+ zones = self._game.getBoards()[oldBoard].getZones()
403
+ for zone in zones.values():
404
+ grid = zone.getGrids()[0]
405
+ if grid is None: continue
406
+
407
+ coord = grid.getLocation(oldLoc)
408
+ if coord is None: continue
409
+
410
+ oldZone = zone['name']
411
+ oldX = coord[0]
412
+ oldY = coord[1]
413
+ break
414
+
415
+ if oldZone is not None:
416
+ basic['properties'] = \
417
+ f'8;'+\
418
+ f'UniqueID;{iden};'+\
419
+ f'OldZone;{oldZone};'+\
420
+ f'OldLocationName;{oldLoc};'+\
421
+ f'OldDeckName;;'+\
422
+ f'OldX;{oldX};'+\
423
+ f'OldY;{oldY};'+\
424
+ f'OldBoard;{oldBoard};'+\
425
+ f'OldMap;{oldMap}'
426
+ else:
427
+ basic['properties'] = \
428
+ f'7;'+\
429
+ f'UniqueID;{iden};'+\
430
+ f'OldLocationName;{oldLoc};'+\
431
+ f'OldDeckName;;'+\
432
+ f'OldX;{oldX};'+\
433
+ f'OldY;{oldY};'+\
434
+ f'OldBoard;{oldBoard};'+\
435
+ f'OldMap;{oldMap}'
436
+
437
+ for trait in traits:
438
+ if trait.ID == TrailTrait.ID:
439
+ trait['map'] = oldMap
440
+ trait['points'] = f'1;{oldX},{oldY}'
441
+ trait['init'] = True
442
+
443
+ # Let user code update the flattened traits
444
+ if update is not None:
445
+ update(name,traits)
446
+ # Wrapper
447
+ wrap = DummyWithTraits(self._game,traits=[])
448
+ wrap.setTraits(*traits,iden=str(iden))
449
+ lines.append(wrap._node.childNodes[0].nodeValue+'\\')
450
+
451
+ layer = -1
452
+ for key,dat in self._stacks.items():
453
+ pids = dat.get('pids',None)
454
+ x = dat['x']
455
+ y = dat['y']
456
+ if pids is None or len(pids) < 1:
457
+ print(f'No pieces at {key[0]},{key[1]}')
458
+ continue
459
+
460
+ iden = self._nextId
461
+ self._nextId += 1
462
+ stack = StackTrait(board=key[0],x=x,y=y,pieceIds=pids,layer=layer)
463
+ layer = 1
464
+ wrap = DummyWithTraits(self._game,traits=[])
465
+ wrap.setTraits(stack,iden=iden)
466
+ lines.append(wrap._node.childNodes[0].nodeValue+'\\')
467
+
468
+ def _otherLines(self,lines):
469
+ '''Add other lines to save'''
470
+ lines.append('UNMASK\tnull')
471
+ if self._game.getPlayerRoster():
472
+ for r in self._game.getPlayerRoster():
473
+ lines.extend(r.encode())
474
+ if self._game.getNotes(single=False):
475
+ for n in self._game.getNotes(single=False):
476
+ lines.extend(n.encode())
477
+ setupStack = False
478
+ for m in self._game.getMaps(asdict=False):
479
+ for bp in m.getBoardPicker(single=False):
480
+ lines.extend(bp.encode())
481
+ if not setupStack:
482
+ atstart = m.getAtStarts(single=False)
483
+ if atstart and len(atstart) > 0:
484
+ lines.append('SETUP_STACK')
485
+ setupStack = True
486
+
487
+ # for tk,tt in self._game.getTurnTracks(asdict=True):
488
+ # lines.extend(tt.encode())
489
+
490
+
491
+ # --------------------------------------------------------------------
492
+ class SaveData(ModuleData):
493
+ def __init__(self,root=None):
494
+ '''Convinience wrapper'''
495
+ super(SaveData,self).__init__(root=root)