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,2728 @@
1
+ #!/usr/bin/env python
2
+ # Script collected from other scripts
3
+ #
4
+ # ../common/singleton.py
5
+ # ../common/verbose.py
6
+ # ../common/verboseguard.py
7
+ # features.py
8
+ # archive.py
9
+ # base.py
10
+ # head.py
11
+ # image.py
12
+ # tile.py
13
+ # piece.py
14
+ # mark.py
15
+ # draw.py
16
+ # cell.py
17
+ # board.py
18
+ # gamebox.py
19
+ # scenario.py
20
+ # player.py
21
+ # windows.py
22
+ # palette.py
23
+ # tray.py
24
+ # extractor.py
25
+ #
26
+ # ====================================================================
27
+ # From ../common/singleton.py
28
+ # ====================================================================
29
+ class Singleton(type):
30
+ '''Meta base class for singletons'''
31
+ _instances = {}
32
+ def __call__(cls, *args, **kwargs):
33
+ '''Create the singleton object or returned existing
34
+
35
+ Parameters
36
+ ----------
37
+ args : tuple
38
+ Arguments
39
+ kwargs : dict
40
+ Keyword arguments
41
+ '''
42
+ if cls not in cls._instances:
43
+ cls._instances[cls] = \
44
+ super(Singleton, cls).__call__(*args, **kwargs)
45
+ return cls._instances[cls]
46
+
47
+ #
48
+ # EOF
49
+ #
50
+ # ====================================================================
51
+ # From ../common/verbose.py
52
+ # --------------------------------------------------------------------
53
+
54
+ class Verbose(metaclass=Singleton):
55
+ def __init__(self,verbose=False):
56
+ '''Singleton for writing message to screen, contigent on setting
57
+
58
+ Parameters
59
+ ----------
60
+ verbose : bool
61
+ Whether to show messages or not
62
+ '''
63
+ self._indent = ''
64
+ self._verbose = verbose
65
+
66
+ def setVerbose(self,verbose):
67
+ '''Set whether to print or not
68
+
69
+ Parameters
70
+ ----------
71
+ verbose : bool
72
+ Whether to show messages or not
73
+ '''
74
+ self._verbose = verbose
75
+
76
+ @property
77
+ def verbose(self):
78
+ '''Test if this is verbose'''
79
+ return self._verbose
80
+
81
+ def message(self,*args,**kwargs):
82
+ '''Write messsage if verbose
83
+
84
+ Parameters
85
+ ----------
86
+ args : tuple
87
+ Arguments
88
+ kwargs : dict
89
+ Keyword arguments
90
+ '''
91
+ if not self._verbose: return
92
+ if not kwargs.pop('noindent', False):
93
+ print(self._indent,end='')
94
+ print(*args,**kwargs)
95
+
96
+ def incr(self):
97
+ '''Increment indention'''
98
+ self._indent += ' '
99
+
100
+ def decr(self):
101
+ '''Decrement indention'''
102
+ if len(self._indent) > 0:
103
+ self._indent = self._indent[:-1]
104
+
105
+ #
106
+ # EOF
107
+ #
108
+ # ====================================================================
109
+ # From ../common/verboseguard.py
110
+ # --------------------------------------------------------------------
111
+
112
+ class VerboseGuard:
113
+ def __init__(self,*args,**kwargs):
114
+ '''A guard pattern that increases verbose indention
115
+
116
+ This is a context manager. The arguments passed are used for
117
+ an initial message, before increasinig indention.
118
+
119
+ Parameters
120
+ ----------
121
+ args : tuple
122
+ Arguments
123
+ kwargs : dict
124
+ Keyword arguments
125
+ '''
126
+ Verbose().message(*args,**kwargs)
127
+
128
+ def __bool_(self):
129
+ '''Test if verbose'''
130
+ return Verbose().verbose
131
+
132
+ def __enter__(self):
133
+ '''Enter context'''
134
+ Verbose().incr()
135
+ return self
136
+
137
+ def __exit__(self,*args):
138
+ '''Exit context'''
139
+ Verbose().decr()
140
+
141
+ @property
142
+ def i(self):
143
+ return Verbose()._indent
144
+
145
+ def __call__(self,*args,**kwargs):
146
+ '''Write a message at current indention level
147
+
148
+ Parameters
149
+ ----------
150
+ args : tuple
151
+ Arguments
152
+ kwargs : dict
153
+ Keyword arguments
154
+ '''
155
+ Verbose().message(*args,**kwargs)
156
+
157
+ #
158
+ # EOF
159
+ #
160
+ # ====================================================================
161
+ # From features.py
162
+ # --------------------------------------------------------------------
163
+
164
+ class Features(metaclass=Singleton):
165
+ def __init__(self):
166
+ self.bmp_zlib = False # wxBMPHandler + Zlib
167
+ self.id_size = 2 # Size of IDs in bytes (1, 2 or 4)
168
+ self.size_size = 4 # Size of sizes in bytes (4 or 8)
169
+ self.sub_size = 2 # Size of sub-sizes in bytes (4 or 8)
170
+ self.square_cells = False # Geomorphic boards, square cells
171
+ self.rotate_unit = False # Geomorphic boards, rotated unit board
172
+ self.piece_100 = False # Pieces w/<= 100 sides
173
+ self.private_board = False #
174
+ self.roll_state = False # serialize roll state
175
+ self.little_endian = True
176
+ # ====================================================================
177
+ # From archive.py
178
+
179
+
180
+
181
+ class BaseArchive:
182
+ WORD_SIZE = 2
183
+
184
+ def __init__(self,filename,mode='rb'):
185
+ '''Read data from a MFT CArchive stored on disk
186
+
187
+ Works as a context manager
188
+ '''
189
+ with VerboseGuard(f'Opening archive {filename}'):
190
+ self._filename = filename
191
+ self._file = open(filename,mode)
192
+ self._i = 0
193
+ self.vmsg = lambda *args : Verbose().message(*args)
194
+ #self.vmsg = lambda *args : None
195
+
196
+ def __enter__(self):
197
+ '''Enter context'''
198
+ return self
199
+
200
+ def __exit__(self,*args,**kwargs):
201
+ '''Exit context'''
202
+ self._file.close()
203
+
204
+ def tell(self):
205
+ pass
206
+
207
+ def read(self,n):
208
+ '''Read n bytes from archive'''
209
+ pass
210
+
211
+ def chr(self,n):
212
+ '''Read n characters from archive'''
213
+ b = self.read(n)
214
+ try:
215
+ c = b.decode()
216
+ self.vmsg(f'char->{c}')
217
+ return c
218
+ except:
219
+ print(f'Failed at {b} ({self._file.tell()})')
220
+ raise
221
+
222
+ def int(self,n):
223
+ '''Read an (unsigned) integer from archive'''
224
+ b = self.read(n)
225
+ i = int.from_bytes(b,'little' if Features().little_endian else 'big')
226
+ self.vmsg(f'int->{i}')
227
+ return i
228
+
229
+ def byte(self):
230
+ '''Read a byte from archive'''
231
+ return self.int(1)
232
+
233
+ def word(self):
234
+ '''Read a word (16bit integer) from archive'''
235
+ w = self.int(BaseArchive.WORD_SIZE)
236
+ self.vmsg(f'word->{w}')
237
+ return w;
238
+
239
+
240
+ def dword(self):
241
+ '''Read a double word (32bit integer) from archive'''
242
+ d = self.int(2*BaseArchive.WORD_SIZE)
243
+ self.vmsg(f'dword->{d}')
244
+ return d
245
+
246
+ def size(self):
247
+ '''Read a size'''
248
+ s = self.int(Features().size_size)
249
+ self.vmsg(f'size->{s}')
250
+ return s
251
+
252
+ def sub_size(self):
253
+ '''Read a size'''
254
+ s = self.int(Features().sub_size)
255
+ self.vmsg(f'sub->{s}')
256
+ return s
257
+
258
+ def count(self):
259
+ '''Read a count'''
260
+ if Features().size_size == 4:
261
+ return self.word()
262
+
263
+ c = self.word()
264
+ if c != 0xFFFF:
265
+ return c
266
+
267
+ c = self.dword()
268
+ if c != 0xFFFFFFFF:
269
+ return c
270
+
271
+ return int(8)
272
+
273
+ def iden(self):
274
+ '''Read an identifier'''
275
+ i = self.int(Features().id_size)
276
+ self.vmsg(f'id->{i}')
277
+ return i
278
+
279
+ def _strlen(self):
280
+ '''Read length of following string from archive'''
281
+ # See https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/arccore.cpp
282
+
283
+ s = 1
284
+ l = self.byte()
285
+
286
+ if l < 0xFF: # Small ASCII string
287
+ self.vmsg(f'slen->{l},{s}')
288
+ return l, s
289
+
290
+ l = self.word()
291
+ if l == 0xFFFE: # Unicode - try again
292
+ s = 2
293
+ l = self.byte()
294
+
295
+ if l < 0xFF: # Small unicode
296
+ self.vmsg(f'slen->{l},{s}')
297
+ return l, s
298
+
299
+
300
+ l = self.word() # Large unicode string
301
+
302
+ if l < 0xFFFF: # Not super long
303
+ self.vmsg(f'slen->{l},{s}')
304
+ return l, s
305
+
306
+ l = self.dword()
307
+
308
+ if l < 0xFFFFFFFF: # Not hyper long
309
+ self.vmsg(f'slen->{l},{s}')
310
+ return l, s
311
+
312
+
313
+ self.vmsg(f'slen->{8},fixed')
314
+ return self.int(8)
315
+
316
+ def str(self):
317
+ '''Read a string from the archive'''
318
+ # See https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/arccore.cpp
319
+ l, s = self._strlen()
320
+ # print(f'Read string of length {l}*{s} at {self.tell()}')
321
+ ret = [self.read(s) for i in range(l)]
322
+ try:
323
+ ret = [c.decode() for c in ret]
324
+ except:
325
+ ret = ['']
326
+ self.vmsg(f'str->"{"".join(ret)}"')
327
+ return ''.join(ret)
328
+
329
+ @property
330
+ def filename(self):
331
+ return self._filename
332
+
333
+ @property
334
+ def path(self):
335
+ from pathlib import Path
336
+ return Path(self._filename)
337
+
338
+ # ====================================================================
339
+ class UnbufferedArchive(BaseArchive):
340
+ def __init__(self,filename,mode='rb'):
341
+ '''Read data from a MFT CArchive stored on dist
342
+
343
+ Works as a context manager
344
+ '''
345
+ super(UnbufferedArchive,self).__init__(filename,mode='rb')
346
+
347
+ def read(self,n):
348
+ '''Read n bytes from archive - directly from file'''
349
+ b = self._file.read(n)
350
+ self.vmsg(f'read->{list(b)}')
351
+ # print(f'{self._i:6d} -> {b}')
352
+ self._i += n
353
+ return b
354
+
355
+ def tell(self):
356
+ return self._file.tell()
357
+
358
+ # ====================================================================
359
+ class BufferedArchive(BaseArchive):
360
+ def __init__(self,filename,mode='rb'):
361
+ '''Read data from a MFT CArchive stored on dist
362
+
363
+ Works as a context manager
364
+ '''
365
+ super(BufferedArchive,self).__init__(filename,mode='rb')
366
+ self._size = 4096
367
+ self._max = self._size
368
+ self._current = self._max
369
+ self._buffer = []
370
+
371
+ def read(self,n):
372
+ with VerboseGuard(f'Read {n} bytes') as g:
373
+ '''Read n bytes from archive - buffered
374
+
375
+ This emulates the behaviour of MFC CArchive::Read
376
+ '''
377
+
378
+ nmax = n
379
+ ntmp = min(nmax, self._max - self._current)
380
+ b = self._buffer[self._current:self._current+ntmp]
381
+ g(f'Take {ntmp} bytes from buffer -> {b}')
382
+ self._current += ntmp
383
+ nmax -= ntmp
384
+
385
+ if nmax != 0:
386
+ g(f'Need to read at least {nmax} from file')
387
+ assert self._current == self._max,\
388
+ f'Something is wrong! {self._current} != ' \
389
+ f'{self._max} (1)'
390
+
391
+ g(f'Missing {nmax} bytes -> ({nmax % self._size})')
392
+ ntmp = nmax - (nmax % self._size)
393
+ nread = 0
394
+ nleft = ntmp
395
+ nbytes = 0
396
+ while True:
397
+ tmpbuf = self._file.read(nleft)
398
+ nbytes = len(tmpbuf)
399
+ nread += nbytes
400
+ nleft -= nbytes
401
+ b += tmpbuf
402
+ g(f'Read {nleft} -> {tmpbuf}')
403
+
404
+ if nbytes <= 0 or nleft <= 0:
405
+ break
406
+
407
+ nmax -= nread
408
+
409
+ if nmax > 0 and nread == ntmp:
410
+ # Last read chunk into buffer and copy
411
+ assert self._current == self._max,\
412
+ f'Something is wrong! {self._current} != ' \
413
+ f'{self._max} (2)'
414
+
415
+ assert nmax < self._size, \
416
+ f'Something is wrong {nmax} >= {self._size}'
417
+
418
+ nlastleft = max(nmax,self._size)
419
+ nlastbytes = 0
420
+ nread = 0
421
+ self._buffer = []
422
+ while True:
423
+ tmpbuf = self._file.read(nlastleft)
424
+ nlastbytes = len(tmpbuf)
425
+ nread += nlastbytes
426
+ nlastleft -= nlastbytes
427
+ self._buffer += tmpbuf
428
+
429
+ if (nlastbytes <= 0) or \
430
+ (nlastleft <= 0) or \
431
+ nread >= ntmp:
432
+ break
433
+
434
+ self._current = 0
435
+ self._max = nread
436
+
437
+ ntmp = min(nmax, self._max - self._current)
438
+ b += self._buffer[self._current:
439
+ self._current+ntmp]
440
+ self._current += ntmp
441
+ nmax -= ntmp
442
+
443
+ g(b)
444
+ return b''.join(b)
445
+
446
+ def tell(self):
447
+ return self._file.tell()
448
+
449
+
450
+ Archive = UnbufferedArchive
451
+ # Archive = BufferedArchive
452
+
453
+ #
454
+ # EOF
455
+ #
456
+ # ====================================================================
457
+ # From base.py
458
+
459
+ # --------------------------------------------------------------------
460
+ class CbFont:
461
+ def __init__(self,ar):
462
+ '''Shared structure that holds font information'''
463
+ # Fonts
464
+ with VerboseGuard('Reading font definition'):
465
+ self._size = ar.word()
466
+ self._flags = ar.word()
467
+ self._family = ar.word()
468
+ self._name = ar.str()
469
+
470
+ def isBold(self):
471
+ return self._flags & 0x1
472
+
473
+ def isItalic(self):
474
+ return self._flags & 0x2
475
+
476
+ def isUnderline(self):
477
+ return self._flags & 0x4
478
+
479
+ def __str__(self):
480
+ return (f'Font:{self._name} ({self._family}) @ '
481
+ f'{self._size} ({self._flags:08x})')
482
+
483
+ # --------------------------------------------------------------------
484
+ class CbManager:
485
+ def __init__(self,ar):
486
+ '''Base class for some managers'''
487
+ with VerboseGuard('Reading general manager'):
488
+ self._foreground = ar.dword()
489
+ self._background = ar.dword()
490
+ self._linewidth = ar.word()
491
+ self._font = CbFont(ar)
492
+ self._reserved = [ar.word() for _ in range(4)]
493
+
494
+ def _readNsub(self,ar,sub_size):
495
+ return ar.int(sub_size)
496
+
497
+ def _readSub(self,ar,cls,sub_size=None):
498
+ if sub_size is None:
499
+ sub_size = Features().sub_size
500
+ with VerboseGuard(f'Reading sub {cls} of manager ({sub_size})'):
501
+ n = self._readNsub(ar,sub_size)
502
+ return [cls(ar) for _ in range(n)]
503
+
504
+ def _strSub(self,title,subs):
505
+ subl = '\n '.join([str(s) for s in subs])
506
+ return f' # {title}: {len(subs)}\n {subl}'
507
+
508
+ def __str__(self):
509
+ return (f' Foreground: {self._foreground:08x}\n'
510
+ f' Background: {self._background:08x}\n'
511
+ f' Linewidth: {self._linewidth}\n'
512
+ f' Font: {self._font}\n'
513
+ f' Reserved: {self._reserved}\n')
514
+
515
+ #
516
+ # EOF
517
+ #
518
+ # ====================================================================
519
+ # From head.py
520
+
521
+ def num_version(major,minor):
522
+ return major * 256 + minor
523
+
524
+ def readVector(ar,cls):
525
+ with VerboseGuard('Reading vector') as g:
526
+ if Features().size_size == 8:
527
+ n = ar.size()
528
+ else:
529
+ n = ar.word()
530
+ if n == 0xFFFF:
531
+ n = ar.dword()
532
+ if n == 0xFFFFFFFF:
533
+ n = ar.int(64)
534
+ g(f'{n} elements')
535
+ return [cls(ar) for _ in range(n)]
536
+
537
+ # ====================================================================
538
+ class GBXHeader:
539
+ BOX = 'GBOX'
540
+ SCENARIO = 'GSCN'
541
+ def __init__(self,ar,expect=BOX):
542
+ '''GBXHeader of file
543
+
544
+ 4 bytes format ID
545
+ 4x1 byte format and program version
546
+
547
+ 8 bytes in total
548
+ '''
549
+ with VerboseGuard('Reading header') as g:
550
+ sig = ar.chr(len(expect))
551
+ assert sig == expect, f'Not a {expect} file: {sig}'
552
+
553
+ self._major = ar.byte()
554
+ self._minor = ar.byte()
555
+ self._programMajor = ar.byte()
556
+ self._programMinor = ar.byte()
557
+ self._vers = num_version(self._major,self._minor)
558
+ g(f'Version {self._major}.{self._minor}')
559
+
560
+ assert self._vers >= num_version(3,0),\
561
+ f'{self._major}.{self._minor} format not supported'
562
+
563
+ if self._vers >= num_version(4,0):
564
+ g(f'Detected version 4.0 or newer, setting some features')
565
+ Features().id_size = 4
566
+ Features().size_size = 8
567
+ Features().sub_size = 8
568
+ Features().square_cells = True
569
+ Features().rotate_unit = True
570
+ Features().piece_100 = True
571
+ Features().private_board = True
572
+ Features().roll_state = True
573
+
574
+
575
+
576
+
577
+
578
+ def __str__(self):
579
+ return ('Header:\n'
580
+ f' Format major version: {self._major}\n'
581
+ f' Format minor version: {self._minor}\n'
582
+ f' Program major version: {self._programMajor}\n'
583
+ f' Program minor version: {self._programMinor}\n')
584
+
585
+
586
+ # --------------------------------------------------------------------
587
+ class GBXStrings:
588
+ def __init__(self,ar):
589
+ '''Map IDs to strings'''
590
+ with VerboseGuard(f'Reading string mappings'):
591
+ strMapN = ar.size()
592
+
593
+ self._id2str = {}
594
+ for _ in range(strMapN):
595
+ key = ar.dword()
596
+ val = ar.str()
597
+
598
+ self._id2str[key] = val
599
+
600
+ def __str__(self):
601
+ return ('Strings:\n'+
602
+ '\n'.join([f' {key:8x}: {val}'
603
+ for key,val in self._id2str.items()]))
604
+
605
+ # --------------------------------------------------------------------
606
+ class GSNStrings:
607
+ def __init__(self,ar):
608
+ '''Map IDs to strings'''
609
+ with VerboseGuard(f'Reading string mappings'):
610
+ strMapN = ar.size()
611
+
612
+ self._id2str = {}
613
+ for _ in range(strMapN):
614
+ key = ar.size()
615
+ val = ar.str()
616
+
617
+ self._id2str[key] = val
618
+
619
+ def __str__(self):
620
+ return ('Strings:\n'+
621
+ '\n'.join([f' {key:8x}: {val}'
622
+ for key,val in self._id2str.items()]))
623
+
624
+ #
625
+ # EOF
626
+ #
627
+ # ====================================================================
628
+ # From image.py
629
+
630
+ # ====================================================================
631
+ class GBXImage:
632
+ def __init__(self,ar,transparent=None,save=None):
633
+ '''A DIB image stored in GameBox'''
634
+ with VerboseGuard('Reading an image') as g:
635
+ size = ar.dword()
636
+
637
+ if size & 0x80000000: # Compressed
638
+ from zlib import decompress
639
+ g(f'Read compressed image')
640
+
641
+ size &= 0x80000000 # Remove flag
642
+ compSize = ar.dword() # Compressed size
643
+ compressed = ar.read(compSize) # Compressed
644
+ buffer = decompress(compressed,bufsize=size)
645
+ #assert len(buffer) == size, \
646
+ # f'Failed to decompress to expected {size}, ' \
647
+ # f'got {len(buffer)}'
648
+
649
+ else:
650
+ buffer = ar.read(size)
651
+
652
+ from PIL import Image as PILImage
653
+ from io import BytesIO
654
+ from numpy import asarray, where, uint8
655
+
656
+ img = PILImage.open(BytesIO(buffer)).convert('RGBA')
657
+
658
+ # If transparancy is defined, clean up the image
659
+ if transparent is None:
660
+ self._img = img
661
+ else:
662
+ g(f'Making #{transparent:06x} transparent')
663
+ dat = asarray(img)
664
+ tr = (transparent >> 16) & 0xFF
665
+ tg = (transparent >> 8) & 0xFF
666
+ tb = (transparent >> 0) & 0xFF
667
+ dat2 = dat.copy()
668
+ dat2[:,:,3] = (255*(dat[:,:,:3]!=[tb,tg,tr]).any(axis=2))\
669
+ .astype(uint8)
670
+
671
+ #if (dat[:,:,3] == dat2[:,:,3]).all():
672
+ # print(f'Transparency operation seemed to have no effect '
673
+ # f'for image')
674
+
675
+ self._img = PILImage.fromarray(dat2)
676
+
677
+ if save is None:
678
+ return
679
+
680
+ self._img.save(save)
681
+
682
+ @classmethod
683
+ def b64encode(cls,img):
684
+ '''Encode image as a base64 data URL'''
685
+ from io import BytesIO
686
+ from base64 import b64encode
687
+
688
+ if img is None:
689
+ return None
690
+
691
+ buffered = BytesIO()
692
+ img.save(buffered,format='PNG')
693
+ data = b64encode(buffered.getvalue())
694
+ if not isinstance(data,str):
695
+ data = data.decode()
696
+
697
+ return 'data:image/png;base64,'+data
698
+
699
+ #
700
+ # EOF
701
+ #
702
+ # ====================================================================
703
+ # From tile.py
704
+
705
+ # ====================================================================
706
+ class GBXTileLocation:
707
+ def __init__(self,ar):
708
+ '''Where a tile can be found'''
709
+ with VerboseGuard('Reading tile location') as g:
710
+ self._sheet = (ar.word() if not Features().size_size == 8 else
711
+ ar.size())
712
+ self._offset = ar.word()
713
+ g(f'Tile location at sheet={self._sheet} offset={self._offset}')
714
+ if self._sheet == 65535: self._sheet = -1
715
+
716
+ def __str__(self):
717
+ return f'{self._sheet:3d} @ {self._offset:6d}'
718
+
719
+
720
+ # --------------------------------------------------------------------
721
+ class GBXTileDefinition:
722
+ def __init__(self,ar):
723
+ '''The definition of a tile'''
724
+ with VerboseGuard('Reading tile definition'):
725
+ self._full = GBXTileLocation(ar)
726
+ self._half = GBXTileLocation(ar)
727
+ self._fill = ar.dword()
728
+
729
+ def __str__(self):
730
+ return f'Full: {self._full}, Half: {self._half}, Fill: {self._fill:08x}'
731
+
732
+
733
+ # --------------------------------------------------------------------
734
+ class GBXTileSet:
735
+ def __init__(self,ar):
736
+ '''A set of tiles'''
737
+ with VerboseGuard('Reading tile set'):
738
+ self._name = ar.str()
739
+ n = ar.word() if Features().size_size != 8 else ar.size()
740
+ self._ids = [ar.iden()
741
+ for _ in range(n)]
742
+
743
+ def __str__(self):
744
+ return (self._name + ':' + ','.join([str(i) for i in self._ids]))
745
+
746
+
747
+ # --------------------------------------------------------------------
748
+ class GBXTileSheet:
749
+ def __init__(self,ar,transparent):
750
+ '''A single image that has multiple tile images in it
751
+
752
+ X,Y are the tile sizes
753
+ '''
754
+ with VerboseGuard('Reading tile sheet'):
755
+ self._x = ar.word()
756
+ self._y = ar.word()
757
+ hasBM = ar.word()
758
+ self._img = GBXImage(ar,transparent) if hasBM else None
759
+
760
+ def sub(self,off):
761
+ if self._img is None:
762
+ return None
763
+
764
+ return self._img._img.crop((0,off,self._x,off+self._y))
765
+
766
+ def __str__(self):
767
+ bm = str(self._img) if self._img is not None else 'None'
768
+ return (f'c=({self._x:4d},{self._y:4d}) bitmap={bm}')
769
+
770
+
771
+ # --------------------------------------------------------------------
772
+ class GBXTileManager(CbManager):
773
+ def __init__(self,ar):
774
+ '''Tile manager (including tiles)'''
775
+ with VerboseGuard('Reading tile manager'):
776
+ self._transparent = ar.dword()
777
+ super(GBXTileManager,self).__init__(ar)
778
+
779
+ ts = lambda ar : GBXTileSheet(ar, self._transparent)
780
+ self._tiledefs = self._readSub(ar,GBXTileDefinition,
781
+ Features().id_size)
782
+ self._tilesets = self._readSub(ar,GBXTileSet)
783
+ self._tilesheets = self._readSub(ar,ts)
784
+ self._toStore = {} # Used in boards, not elsewhere
785
+
786
+ def image(self,tileID):
787
+ if tileID is None:
788
+ return None
789
+ if tileID == 0xFFFF:
790
+ return None
791
+
792
+ tileDef = self._tiledefs[tileID]
793
+ tileSht = self._tilesheets[tileDef._full._sheet]
794
+ img = tileSht.sub(tileDef._full._offset)
795
+ return img
796
+
797
+ def store(self,tileID):
798
+ filename = self._toStore.get(tileID,{}).get('filename',None)
799
+ if filename is None:
800
+ filename = f'tile_{tileID:04d}.png'
801
+ self._toStore[tileID] = {
802
+ 'filename': filename,
803
+ 'image' : self.image(tileID)
804
+ }
805
+
806
+ return filename
807
+
808
+
809
+ def __str__(self):
810
+ return ('Tile manager:\n'
811
+ + f' Transparent: {self._transparent:08x}\n'
812
+ + super(GBXTileManager,self).__str__()
813
+ + self._strSub('tiles',self._tiledefs) + '\n'
814
+ + self._strSub('tile sets',self._tilesets) + '\n'
815
+ + self._strSub('tile sheets',self._tilesheets))
816
+
817
+ #
818
+ # EOF
819
+ #
820
+ # ====================================================================
821
+ # From piece.py
822
+ # ====================================================================
823
+ class GBXPieceDef:
824
+ def __init__(self,ar):
825
+ '''Definition of a piece
826
+
827
+ FRONT and BACK are tile IDs
828
+
829
+ FLAGS is ...
830
+ '''
831
+ with VerboseGuard(f'Reading piece definition'):
832
+ if Features().piece_100:
833
+ n = ar.size()
834
+ self._ids = [ar.iden() for _ in range(n)]
835
+ else:
836
+ self._ids = [ar.word(),ar.word()]
837
+ self._flags = ar.word()
838
+
839
+
840
+ @property
841
+ def _front(self):
842
+ return self._ids[0]
843
+
844
+ @property
845
+ def _back(self):
846
+ return self._ids[1] if len(self._ids) > 1 else 0
847
+
848
+ def __str__(self):
849
+ return f'Piece: {self._front:04x},{self._back:04x},{self._flags:04x}'
850
+
851
+ # --------------------------------------------------------------------
852
+ class GBXPieceSet:
853
+ def __init__(self,ar):
854
+ '''Set of pieces'''
855
+ with VerboseGuard(f'Reading piece set'):
856
+ self._name = ar.str()
857
+ n = ar.sub_size()
858
+ self._pieces = [ar.iden() for _ in range(n)]
859
+
860
+ def __len__(self):
861
+ return len(self._pieces)
862
+
863
+ def __str__(self):
864
+ return (f'{self._name}: '+','.join([str(p) for p in self._pieces]))
865
+
866
+ # --------------------------------------------------------------------
867
+ class GBXPieceManager:
868
+ def __init__(self,ar):
869
+ '''Manager of pieces'''
870
+ with VerboseGuard(f'Reading piece manager') as g:
871
+ self._reserved = [ar.word() for _ in range(4)]
872
+ g(f'Reserved are {self._reserved}')
873
+ n = ar.iden();
874
+ g(f'Will read {n} pieces')
875
+ self._pieces = [GBXPieceDef(ar) for _ in range(n)]
876
+ n = ar.sub_size()
877
+ g(f'Will read {n} sets')
878
+ self._sets = [GBXPieceSet(ar) for _ in range(n)]
879
+
880
+ def __len__(self):
881
+ return len(self._sets)
882
+
883
+ def toDict(self,tileManager,strings):
884
+ from math import log10, ceil
885
+ with VerboseGuard(f'Creating dict from piece manager') as gg:
886
+ gg(f'Has {len(self._sets)} and {len(self._pieces)} pieces')
887
+ setDigits = int(ceil(log10(len(self)+.5)))
888
+ pieceDigits = 1
889
+ for pieceSet in self._sets:
890
+ pieceDigits = max(pieceDigits,
891
+ int(ceil(log10(len(pieceSet)+.5))))
892
+
893
+ cnt = 0
894
+ piecesMap = {}
895
+ setList = []
896
+ ret = {'map': piecesMap,
897
+ 'sets': setList }
898
+
899
+ for ips, pieceSet in enumerate(self._sets):
900
+ with VerboseGuard(f'Creating dict from piece set '
901
+ f'{pieceSet._name}') as g:
902
+ setPrefix = f'piece_{ips:0{setDigits}d}'
903
+ idList = []
904
+ setDict = { 'description': pieceSet._name.strip(),
905
+ 'pieces': idList }
906
+ setList.append(setDict)
907
+
908
+ for ipc, pieceID in enumerate(pieceSet._pieces):
909
+
910
+ piecePrefix = f'{setPrefix}_{ipc:0{pieceDigits}d}'
911
+ pieceDef = self._pieces[pieceID]
912
+ tmpStr = strings._id2str.get(pieceID,'')
913
+ pieceDesc = tmpStr.replace('\r','').replace('\n',', ').replace('/','\\/')
914
+ pieceDict = {}
915
+ if pieceDesc != '':
916
+ pieceDict['description'] = pieceDesc
917
+ cnt += 1
918
+ # pieceList.append(pieceDict)
919
+ idList .append(pieceID)
920
+
921
+ # print(f'{pd} => "{tr}"')
922
+ for tileId,which in zip([pieceDef._front,
923
+ pieceDef._back],
924
+ ['front',
925
+ 'back']):
926
+ img = tileManager.image(tileId)
927
+
928
+ if img is None:
929
+ continue
930
+
931
+ sav = f'{piecePrefix}_{which}.png'
932
+ setname = pieceSet._name.strip()\
933
+ .replace('\n',' ')\
934
+ .replace('\r',' ')\
935
+ .replace('/','\\/')
936
+ gg(f'Set name, escaped: "{setname}"')
937
+ pieceDict[which] = {
938
+ 'image': img,
939
+ 'filename': sav,
940
+ 'set': setname }
941
+ piecesMap[pieceID] = pieceDict
942
+ g(f'{pieceID}: {pieceDict}')
943
+
944
+ gg(f'{list(piecesMap.keys())}')
945
+ return ret
946
+
947
+
948
+ def __str__(self):
949
+ return ('Piece manager:\n'
950
+ +f'Reserved: {self._reserved}\n'
951
+ +f'# pieces: {len(self._pieces)}\n '
952
+ +'\n '.join([str(p) for p in self._pieces])+'\n'
953
+ +f'# piece sets: {len(self._sets)}\n '
954
+ +'\n '.join([str(p) for p in self._sets])
955
+ )
956
+
957
+ # --------------------------------------------------------------------
958
+ class GSNPieceEntry:
959
+ def __init__(self,ar,vers,i):
960
+ '''Manager of pieces'''
961
+ with VerboseGuard(f'Reading piece # {i:3d} ({vers//256}.{vers%256})'):
962
+ self._side = ar.byte()
963
+ self._facing = ar.word()
964
+ self._owner = ar.word() if vers < num_version(3,10) else ar.dword()
965
+
966
+ def __str__(self):
967
+ return f'Piece: {self._side}, {self._facing:3d}, {self._owner:08x}'
968
+
969
+ # --------------------------------------------------------------------
970
+ class GSNPieceTable:
971
+ def __init__(self,ar,vers):
972
+ '''Manager of pieces'''
973
+ with VerboseGuard(f'Reading piece table'):
974
+ self._reserved = [ar.word() for _ in range(4)]
975
+ n = ar.word()#sub_size()
976
+ if Features().piece_100:
977
+ dummy = ar.word();
978
+ self._pieces = [GSNPieceEntry(ar,vers,i) for i in range(n)]
979
+
980
+ def __str__(self):
981
+ pl = '\n '.join([str(s) for s in self._pieces])
982
+ return (f'Piece table: {self._reserved} {len(self._pieces)}'
983
+ f'\n {pl}\n')
984
+ #
985
+ # EOF
986
+ #
987
+ # ====================================================================
988
+ # From mark.py
989
+
990
+ # ====================================================================
991
+ class GBXMarkDef:
992
+ def __init__(self,ar):
993
+ '''Definition of a mark'''
994
+ with VerboseGuard(f'Reading mark definition'):
995
+ self._id = ar.iden()
996
+ self._flags = ar.word()
997
+
998
+ def __str__(self):
999
+ return f'Mark: {self._id:04x},{self._flags:04x}'
1000
+
1001
+ # --------------------------------------------------------------------
1002
+ class GBXMarkSet:
1003
+ def __init__(self,ar):
1004
+ '''Set of marks'''
1005
+ with VerboseGuard(f'Reading mark set'):
1006
+ self._name = ar.str()
1007
+ self._viz = ar.word()
1008
+ n = ar.sub_size()
1009
+ self._marks = [ar.iden() for _ in range(n)]
1010
+
1011
+ def __len__(self):
1012
+ return len(self._marks)
1013
+
1014
+ def __str__(self):
1015
+ return (f'{self._name}: '+','.join([str(p) for p in self._marks]))
1016
+
1017
+ # --------------------------------------------------------------------
1018
+ class GBXMarkManager:
1019
+ def __init__(self,ar):
1020
+ '''Manager of marks'''
1021
+ with VerboseGuard(f'Reading mark manager'):
1022
+ self._reserved = [ar.word() for _ in range(4)]
1023
+ n = ar.iden()
1024
+ self._marks = [GBXMarkDef(ar) for _ in range(n)]
1025
+ n = ar.sub_size()
1026
+ self._sets = [GBXMarkSet(ar) for _ in range(n)]
1027
+
1028
+ def __len__(self):
1029
+ return len(self._sets)
1030
+
1031
+ def toDict(self,tileManager,strings):
1032
+ from math import log10, ceil
1033
+ with VerboseGuard(f'Creating dict from mark manager'):
1034
+ setDigits = int(ceil(log10(len(self)+.5)))
1035
+ markDigits = 1
1036
+ for markSet in self._sets:
1037
+ markDigits = max(markDigits,
1038
+ int(ceil(log10(len(markSet)+.5))))
1039
+
1040
+ marksMap = {}
1041
+ setList = []
1042
+ ret = {'map': marksMap,
1043
+ 'sets': setList }
1044
+
1045
+ for ips, markSet in enumerate(self._sets):
1046
+ with VerboseGuard(f'Creating dict mark set {markSet._name}'):
1047
+ setPrefix = f'mark_{ips:0{setDigits}d}'
1048
+ idList = []
1049
+ setDict = { 'description': markSet._name.strip(),
1050
+ 'marks': idList }
1051
+ setList.append(setDict)
1052
+
1053
+ for ipc, markID in enumerate(markSet._marks):
1054
+ markPrefix = f'{setPrefix}_{ipc:0{markDigits}d}'
1055
+ markDef = self._marks[markID]
1056
+ tmpStr = strings._id2str.get(markID|0xF0000000,'')
1057
+ markDesc = tmpStr.replace('\r','').replace('\n',', ')
1058
+ markDict = {}
1059
+ if markDesc != '':
1060
+ markDict['description'] = markDesc
1061
+ marksMap[markID] = markDict
1062
+ idList .append(markID)
1063
+
1064
+ img = tileManager.image(markDef._id)
1065
+
1066
+ if img is None:
1067
+ continue
1068
+
1069
+ sav = f'{markPrefix}.png'
1070
+ markDict.update({'image': img,
1071
+ 'filename': sav })
1072
+
1073
+ return ret
1074
+
1075
+ def __str__(self):
1076
+ return ('Mark manager:\n'
1077
+ +f'Reserved: {self._reserved}\n'
1078
+ +f'# marks: {len(self._marks)}\n '
1079
+ +'\n '.join([str(p) for p in self._marks])+'\n'
1080
+ +f'# mark sets: {len(self._sets)}\n '
1081
+ +'\n '.join([str(p) for p in self._sets])
1082
+ )
1083
+
1084
+ #
1085
+ # EOF
1086
+ #
1087
+ # ====================================================================
1088
+ # From draw.py
1089
+
1090
+ # ====================================================================
1091
+ class GBXDraw:
1092
+ def __init__(self,ar):
1093
+ '''Base class for drawing objects'''
1094
+ self._flags = ar.dword()
1095
+ self._left = ar.word()
1096
+ self._top = ar.word()
1097
+ self._right = ar.word()
1098
+ self._bottom = ar.word()
1099
+
1100
+ def isSecondPass(self):
1101
+ return self._flags & 0x00000008
1102
+
1103
+ def bbWidth(self):
1104
+ return self._right - self._left
1105
+
1106
+ def bbHeight(self):
1107
+ return self._bottom - self._top
1108
+
1109
+ def centerX(self):
1110
+ return (self._left + self._right)//2
1111
+
1112
+ def centerY(self):
1113
+ return (self._top + self._bottom)//2
1114
+
1115
+ def center(self):
1116
+ return (self.centerX(),self.centerY())
1117
+
1118
+ def upperLeft(self):
1119
+ return (self._left,self._top)
1120
+
1121
+ def lowerRight(self):
1122
+ return (self._right,self._bottom)
1123
+
1124
+ def bbSize(self):
1125
+ return (self.bbWidth(),self.bbHeight())
1126
+
1127
+ @classmethod
1128
+ def hex(cls,val):
1129
+ if val == 0xFF000000:
1130
+ h = 'none'
1131
+ else:
1132
+ b = (val >> 16) & 0xFF
1133
+ g = (val >> 8) & 0xFF
1134
+ r = (val >> 0) & 0xFF
1135
+ h = f'rgb({r},{g},{b})'
1136
+ return h
1137
+
1138
+ def toDict(self,calc):
1139
+ return None
1140
+
1141
+ def baseDict(self):
1142
+ return {
1143
+ 'left': self._left,
1144
+ 'top': self._top,
1145
+ 'right': self._right,
1146
+ 'bottom': self._bottom,
1147
+ 'x': self.centerX(),
1148
+ 'y': self.centerY()
1149
+ }
1150
+
1151
+ def svg(self,dwg,g,defmap):
1152
+ print(f'{self.__class__}.svg method not implemented')
1153
+ pass
1154
+
1155
+ def __str__(self):
1156
+ return (f'Flags:{self._flags:08x} '
1157
+ + f'({self._left},{self._top})x({self._right},{self._bottom})')
1158
+
1159
+ # --------------------------------------------------------------------
1160
+ class GBXRectangle(GBXDraw):
1161
+ def __init__(self,ar):
1162
+ '''Draw a rectangle'''
1163
+ with VerboseGuard(f'Reading rectangle'):
1164
+ super(GBXRectangle,self).__init__(ar)
1165
+ self._fill = ar.dword()
1166
+ self._line = ar.dword()
1167
+ self._width = ar.word()
1168
+
1169
+ def svg(self,dwg,g,defmap):
1170
+ r = g.add(dwg.rect(insert=(self.upperLeft()),
1171
+ size=(self.bbSize()),
1172
+ fill=self.hex(self._fill),
1173
+ stroke=self.hex(self._line),
1174
+ stroke_width=self._width))
1175
+
1176
+ def __str__(self):
1177
+ return 'Rectangle: '+super(GBXRectangle,self).__str__()
1178
+
1179
+ # --------------------------------------------------------------------
1180
+ class GBXEllipse(GBXRectangle):
1181
+ def __init__(self,ar):
1182
+ '''Draw an ellipse'''
1183
+ with VerboseGuard(f'Reading ellipse'):
1184
+ super(GBXEllipse,self).__init__(ar)
1185
+
1186
+ def svg(self,dwg,g,defmap):
1187
+ '''Create SVG object'''
1188
+ g.add(dwg.ellipse(center=(self.centerX(),self.centerY()),
1189
+ r=(self.bbWidth(),self.bbHeight()),
1190
+ fill=self.hex(self._fill),
1191
+ stroke=self.hex(self._line),
1192
+ stroke_width=self._width))
1193
+ def __str__(self):
1194
+ return 'Ellipse: '+super(GBXRectangle,self).__str__()
1195
+
1196
+ # --------------------------------------------------------------------
1197
+ class GBXLine(GBXDraw):
1198
+ def __init__(self,ar):
1199
+ '''Draw a line'''
1200
+ with VerboseGuard(f'Reading line'):
1201
+ super(GBXLine,self).__init__(ar)
1202
+ self._x0 = ar.word()
1203
+ self._y0 = ar.word()
1204
+ self._x1 = ar.word()
1205
+ self._y1 = ar.word()
1206
+ self._line = ar.dword()
1207
+ self._width = ar.word()
1208
+
1209
+ def svg(self,dwg,g,defmap):
1210
+ '''Create SVG object'''
1211
+ g.add(dwg.line(start=(self._x0,self._y0),
1212
+ end=(self._x1,self._y1),
1213
+ stroke=self.hex(self._line),
1214
+ stroke_width=self._width))
1215
+
1216
+
1217
+ def __str__(self):
1218
+ return 'Line: ' + super(GBXLine,self).__str__()
1219
+ # f'({self._x0},{self._y0}) -> ({self._x1},{self._y1})')
1220
+
1221
+
1222
+ # --------------------------------------------------------------------
1223
+ class GBXTile(GBXDraw):
1224
+ def __init__(self,ar):
1225
+ '''Draw a tile'''
1226
+ with VerboseGuard(f'Reading tile'):
1227
+ super(GBXTile,self).__init__(ar)
1228
+ self._id = ar.word()
1229
+
1230
+ def svgDef(self,dwg,tileManager,markManager,defmap):
1231
+ '''Create SVG definition from image'''
1232
+ if self._id in defmap:
1233
+ return
1234
+
1235
+ img = tileManager.image(self._id)
1236
+ data = GBXImage.b64encode(img)
1237
+ if data is None:
1238
+ return
1239
+
1240
+ iden = f'tile_{self._id:04x}'
1241
+ img = dwg.defs.add(dwg.image(id=iden,href=(data),
1242
+ size=(img.width,img.height)))
1243
+ defmap[self._id] = img
1244
+
1245
+ def svg(self,dwg,g,defmap):
1246
+ '''Create SVG object'''
1247
+ if self._id not in defmap: return
1248
+
1249
+ g.add(dwg.use(defmap[self._id],
1250
+ insert=(self._left,self._top)))
1251
+
1252
+ def __str__(self):
1253
+ return f'Tile: {self._id} ' + super(GBXTile,self).__str__()
1254
+
1255
+ # --------------------------------------------------------------------
1256
+ class GBXText(GBXDraw):
1257
+ def __init__(self,ar):
1258
+ '''Draw text'''
1259
+ with VerboseGuard(f'Reading text'):
1260
+ super(GBXText,self).__init__(ar)
1261
+ self._angle = ar.word()
1262
+ self._color = ar.dword()
1263
+ self._text = ar.str()
1264
+ self._font = CbFont(ar)
1265
+
1266
+ def svg(self,dwg,g,defmap):
1267
+ '''Create SVG object'''
1268
+ g.add(dwg.text(self._text,
1269
+ insert=(self._left,self._bottom),
1270
+ rotate=[self._angle],
1271
+ fill=self.hex(self._color),
1272
+ font_family='monospace' if self._font._name == '' else self._font._name,
1273
+ font_size=self._font._size,
1274
+ font_weight='bold' if self._font.isBold() else 'normal',
1275
+ font_style='italic' if self._font.isItalic() else 'normal',
1276
+ text_decoration='underline' if self._font.isUnderline() else 'none'))
1277
+
1278
+ def __str__(self):
1279
+ return f'Text: "{self._text}" '+super(GBXText,self).__str__()
1280
+
1281
+ # --------------------------------------------------------------------
1282
+ class GBXPolyline(GBXDraw):
1283
+ def __init__(self,ar):
1284
+ '''Draw a polyline'''
1285
+ with VerboseGuard(f'Reading polyline'):
1286
+ super(GBXPolyline,self).__init__(ar)
1287
+ self._fill = ar.dword()
1288
+ self._line = ar.dword()
1289
+ self._width = ar.word()
1290
+ n = (ar.word() if Features().size_size != 8 else
1291
+ ar.size())
1292
+ self._points = [[ar.word(),ar.word()] for _ in range(n)]
1293
+
1294
+ def svg(self,dwg,g,defmap):
1295
+ '''Create SVG object'''
1296
+ g.add(dwg.polyline(self._points,
1297
+ fill=self.hex(self._fill),
1298
+ stroke=self.hex(self._line),
1299
+ stroke_width=self._width))
1300
+
1301
+ def __str__(self):
1302
+ return f'Polyline: {len(self._points)} '+super(GBXPolyline,self).__str__()
1303
+
1304
+ # --------------------------------------------------------------------
1305
+ class GBXBitmap(GBXDraw):
1306
+ CNT = 0
1307
+
1308
+ def __init__(self,ar):
1309
+ '''Draw a bitmap'''
1310
+ with VerboseGuard(f'Reading bitmap'):
1311
+ super(GBXBitmap,self).__init__(ar)
1312
+ sav = f'B{GBXBitmap.CNT:04d}.png'
1313
+ GBXBitmap.CNT += 1
1314
+ self._scale = ar.word()
1315
+ self._img = GBXImage(ar,save=None)
1316
+
1317
+ def svg(self,dwg,g,defmap):
1318
+ '''Create SVG object'''
1319
+ data = GBXImage.b64encode(self._img._img)
1320
+ size = self._img._img.width, self._img._img.height
1321
+ g.add(dwg.image(insert=(self._left,self._top),
1322
+ size=size,
1323
+ href=(data)))
1324
+
1325
+ def __str__(self):
1326
+ return f'Bitmap: {self._img} ' + super(GBXBitmap,self).__str__()
1327
+
1328
+ # --------------------------------------------------------------------
1329
+ class GBXPiece(GBXDraw):
1330
+ def __init__(self,ar):
1331
+ '''Draw a piece'''
1332
+ with VerboseGuard(f'Reading piece (draw)'):
1333
+ super(GBXPiece,self).__init__(ar)
1334
+ self._id = ar.iden()
1335
+
1336
+ def toDict(self,calc=None):
1337
+ d = {'type': 'Piece',
1338
+ 'id': self._id,
1339
+ 'pixel': self.baseDict()
1340
+ }
1341
+ if calc is not None:
1342
+ d['grid'] = calc(*self.center())
1343
+ return d
1344
+
1345
+ def __str__(self):
1346
+ return f'Piece: {self._id} ' + super(GBXPiece,self).__str__()
1347
+
1348
+ # --------------------------------------------------------------------
1349
+ class GBXMark(GBXDraw):
1350
+ def __init__(self,ar):
1351
+ '''Draw a mark tile'''
1352
+ with VerboseGuard(f'Reading mark (draw)'):
1353
+ super(GBXMark,self).__init__(ar)
1354
+ self._id = ar.size()
1355
+ self._mid = ar.iden()
1356
+ self._ang = ar.word()
1357
+
1358
+ def toDict(self,calc=None):
1359
+ d = {'type': 'Mark',
1360
+ 'id': self._mid,
1361
+ 'pixel': self.baseDict()
1362
+ }
1363
+ if calc is not None:
1364
+ d['grid'] = calc(*self.center())
1365
+ return d
1366
+ def svgDef(self,dwg,tileManager,markManager,defmap):
1367
+ '''Create SVG def from mark'''
1368
+ if self._id in defmap:
1369
+ return
1370
+
1371
+ data = GBXImage.b64encode(tileManager.image(self._id))
1372
+ if data is None:
1373
+ return
1374
+
1375
+ iden = f'mark_{self._id:04x}'
1376
+ img = dwg.defs.add(dwg.image(id=iden,href=(data)))
1377
+ defmap[self._id] = img
1378
+
1379
+ def svg(self,dwg,g,defmap):
1380
+ '''Create SVG object'''
1381
+ if self._id not in defmap: return
1382
+
1383
+ g.add(dwg.use(defmap[self._id],
1384
+ insert=(self._left,self._top)))
1385
+
1386
+ def __str__(self):
1387
+ return f'Mark: {self._id}/{self._mid} ' + super(GBXMark,self).__str__()
1388
+
1389
+ # --------------------------------------------------------------------
1390
+ class GBXLineObj(GBXLine):
1391
+ def __init__(self,ar):
1392
+ '''Line object via reference'''
1393
+ with VerboseGuard(f'Reading line object'):
1394
+ super(GBXLineObj,self).__init__(ar)
1395
+
1396
+ self._id = ar.iden()
1397
+
1398
+ def __str__(self):
1399
+ return f'Line: {self._id} ' + super(GBXLineObj,self).__str__()
1400
+
1401
+
1402
+ # --------------------------------------------------------------------
1403
+ class GBXDrawList:
1404
+ RECT = 0
1405
+ ELLIPSE = 1
1406
+ LINE = 2
1407
+ TILE = 3
1408
+ TEXT = 4
1409
+ POLYLINE = 5
1410
+ BITMAP = 6
1411
+ PIECE = 0x80
1412
+ MARK = 0x81
1413
+ LINEOBJ = 0x82
1414
+
1415
+ TMAP = { RECT: GBXRectangle,
1416
+ ELLIPSE: GBXEllipse,
1417
+ LINE: GBXLine,
1418
+ TILE: GBXTile,
1419
+ TEXT: GBXText,
1420
+ POLYLINE: GBXPolyline,
1421
+ BITMAP: GBXBitmap,
1422
+ PIECE: GBXPiece,
1423
+ MARK: GBXMark,
1424
+ LINEOBJ: GBXLineObj}
1425
+
1426
+ def __init__(self,ar):
1427
+ '''A list of drawn objects'''
1428
+ with VerboseGuard(f'Reading draw list'):
1429
+ n = ar.sub_size()
1430
+
1431
+ self._obj = [self._readObj(ar) for n in range(n)]
1432
+
1433
+ def toDict(self,calc=None):
1434
+ with VerboseGuard(f'Making dictionary from draw list at pass'):
1435
+ ret = []
1436
+ for i in self._obj:
1437
+ d = i.toDict(calc)
1438
+ if d is None:
1439
+ continue
1440
+
1441
+ ret.append(d)
1442
+
1443
+ return ret
1444
+
1445
+ def _readObj(self,ar):
1446
+ '''Read one object'''
1447
+ tpe = ar.word()
1448
+ cls = self.TMAP.get(tpe,None)
1449
+ if cls is None:
1450
+ raise RuntimeError(f'Unknown type of draw: {tpe}')
1451
+
1452
+ return cls(ar)
1453
+
1454
+ def svgDefs(self,dwg,tileManager,markManager,defmap):
1455
+ '''Create SVG defs'''
1456
+ with VerboseGuard(f'Create SVG defs from draw list'):
1457
+ for i in self._obj:
1458
+ if type(i) not in [GBXTile,GBXMark]: continue
1459
+
1460
+ i.svgDef(dwg,tileManager,markManager,defmap)
1461
+
1462
+ def svg(self,dwg,g,passNo,defmap):
1463
+ '''Create SVG objects'''
1464
+ with VerboseGuard(f'Drawing SVG from draw list at pass {passNo}'
1465
+ f' ({len(self._obj)} objects)') as gg:
1466
+ for i in self._obj:
1467
+ if passNo == 1 and i.isSecondPass():
1468
+ continue
1469
+ elif passNo == 2 and not i.isSecondPass():
1470
+ continue
1471
+ gg(f'Drawing {i}')
1472
+ i.svg(dwg,g,defmap)
1473
+
1474
+ def __str__(self):
1475
+ return '\n '.join([str(o) for o in self._obj])
1476
+
1477
+ #
1478
+ # EOF
1479
+ #
1480
+ # ====================================================================
1481
+ # From cell.py
1482
+
1483
+ # ====================================================================
1484
+ class GBXCellGeometry:
1485
+ RECTANGLE = 0
1486
+ HORIZONTAL_BRICK = 1
1487
+ VERTICAL_BRICK = 2
1488
+ HEXAGON = 3
1489
+ SIDEWAYS_HEXAGON = 4
1490
+ STAGGER_OUT = 0
1491
+ STAGGER_IN = 1
1492
+ TYPES = {
1493
+ RECTANGLE : 'rectangle',
1494
+ HORIZONTAL_BRICK: 'horizontal brick',
1495
+ VERTICAL_BRICK : 'vertical brick',
1496
+ HEXAGON : 'hexagon',
1497
+ SIDEWAYS_HEXAGON: 'sideways hexagon'
1498
+ }
1499
+ STAGGERS = {
1500
+ STAGGER_OUT: 'out',
1501
+ STAGGER_IN: 'in'
1502
+ }
1503
+
1504
+ def __init__(self,ar):
1505
+ '''The geometry of cells'''
1506
+ with VerboseGuard('Reading cell geometry'):
1507
+ from numpy import max
1508
+
1509
+ self._type = ar.word()
1510
+ self._stagger = ar.word()
1511
+ self._left = ar.word()
1512
+ self._top = ar.word()
1513
+ self._right = ar.word()
1514
+ self._bottom = ar.word()
1515
+ n = 7 if self._type > 2 else 5
1516
+ self._points = [[ar.word(),ar.word()] for _ in range(n)]
1517
+ size = max(self._points,axis=0)
1518
+ self._dx = int(size[0])
1519
+ self._dy = int(size[1])
1520
+ self._size = [self._dx,self._dy]
1521
+
1522
+ if self._type == self.HEXAGON:
1523
+ self._dx = int(0.75 * self._dx)
1524
+ elif self._type == self.SIDEWAYS_HEXAGON:
1525
+ self._dy = int(0.75 * self._dy)
1526
+
1527
+ def toDict(self):
1528
+ from numpy import max
1529
+ return {'shape': self.TYPES.get(self._type,''),
1530
+ 'stagger': self.STAGGERS.get(self._stagger,''),
1531
+ 'size': self._size,
1532
+ 'bounding box (ltrb)':
1533
+ (self._left, self._top, self._right, self._bottom),
1534
+ 'points': self._points }
1535
+
1536
+ def svgDef(self,dwg):
1537
+ with VerboseGuard('Defining SVG cell geometry'):
1538
+ if self._type in [0,1,2]:
1539
+ return dwg.defs.add(dwg.rect(id='cell',
1540
+ size=(self._right-self._left,
1541
+ self._bottom-self._top)))
1542
+
1543
+ return dwg.defs.add(dwg.polygon(self._points,id='cell'))
1544
+
1545
+ def translate(self,row,col,center=False):
1546
+ x = col * self._dx
1547
+ y = row * self._dy
1548
+ if self._type == self.RECTANGLE: # No offset for rectangles
1549
+ return x,y
1550
+ if self._type in [self.HORIZONTAL_BRICK,self.SIDEWAYS_HEXAGON]:
1551
+ x += self._dx//2 if (row % 2) != self._stagger else 0
1552
+ if self._type in [self.VERTICAL_BRICK,self.HEXAGON]:
1553
+ y += self._dy//2 if (col % 2) != self._stagger else 0
1554
+ if center:
1555
+ x += self._size[0]//2
1556
+ y += self._size[1]//2
1557
+ return x,y
1558
+
1559
+ def inverse(self,x,y):
1560
+ col = x / self._dx
1561
+ row = y / self._dy
1562
+ if self._type in [self.HORIZONTAL_BRICK,self.SIDEWAYS_HEXAGON]:
1563
+ col -= .5 if (int(row) % 2) != self._stagger else 0
1564
+ if self._type in [self.VERTICAL_BRICK,self.HEXAGON]:
1565
+ row -= .5 if (int(col) % 2) != self._stagger else 0
1566
+
1567
+ # CyberBoard start at 1
1568
+ return int(row)+1, int(col)+1
1569
+
1570
+
1571
+ def boardSize(self,nrows,ncols):
1572
+ w = ncols * self._dx
1573
+ h = nrows * self._dy
1574
+
1575
+ if self._type in [2,3]:
1576
+ h += self._dy // 2
1577
+ if self._type in [1,4]:
1578
+ w += self._dx // 2
1579
+ if self._type == 3:
1580
+ w += self._dx // 3
1581
+ if self._type == 4:
1582
+ h += self._dy // 3
1583
+
1584
+ return w+1,h+1
1585
+
1586
+ def __str__(self):
1587
+ return (f'type: {self.TYPES.get(self._type,"")} '
1588
+ + f'stagger: {self.STAGGERS.get(self._stagger,"")} '
1589
+ + f'({self._left},{self._top})x({self._right},{self._bottom}) '
1590
+ + f': [{self._points}]')
1591
+
1592
+
1593
+ # --------------------------------------------------------------------
1594
+ class GBXCell:
1595
+ def __init__(self,ar,row,column):
1596
+ '''A single cell'''
1597
+ with VerboseGuard(f'Reading cell row={row} column={column}'):
1598
+ self._row = row
1599
+ self._column = column
1600
+ if Features().id_size == 4:
1601
+ self._is_tile = ar.byte();
1602
+ self._tile = ar.dword()
1603
+ if Features().id_size != 4:
1604
+ self._is_tile = (self._tile >> 16) == 0xFFFF;
1605
+ if self._is_tile:
1606
+ self._tile = self._tile & 0xFFFF
1607
+
1608
+ def tileID(self):
1609
+ if not self._is_tile:
1610
+ return None
1611
+ return self._tile
1612
+
1613
+ def color(self):
1614
+ if self._is_tile:
1615
+ return None
1616
+ return GBXDraw.hex(self._tile)
1617
+
1618
+ def toDict(self,tileManager,calc=None):
1619
+ d = {'row': self._row,
1620
+ 'column': self._column}
1621
+ if not self._is_tile:
1622
+ d['color'] = GBXDraw.hex(self._tile)
1623
+ else:
1624
+ d['tile'] = tileManager.store(self._tile)
1625
+ if calc is not None:
1626
+ d['pixel'] = calc.translate(self._row,self._column,True)
1627
+ return d
1628
+
1629
+
1630
+ def svgDef(self,dwg,tileManager,ptm):
1631
+ tileID = self.tileID()
1632
+ if tileID is None:
1633
+ return
1634
+
1635
+ if tileID in ptm: # Have it
1636
+ return
1637
+
1638
+ with VerboseGuard(f'Defining SVG pattern'):
1639
+ img = tileManager.image(tileID)
1640
+ data = GBXImage.b64encode(img)
1641
+ if data is None:
1642
+ return
1643
+
1644
+ iden = f'terrain_{tileID:04x}'
1645
+ pat = dwg.defs.add(dwg.pattern(id=iden,
1646
+ size=(img.width,img.height)))
1647
+ pat.add(dwg.image(href=(data)))
1648
+ ptm[tileID] = pat
1649
+
1650
+ def svg(self,dwg,g,cell,geom,ptm):
1651
+ tileID = self.tileID()
1652
+ if tileID is not None:
1653
+ fill = ptm[tileID].get_paint_server()
1654
+ else:
1655
+ fill = self.color()
1656
+
1657
+ trans = geom.translate(self._row,self._column)
1658
+ iden = f'cell_bg_{self._column:03d}{self._row:03d}'
1659
+ g.add(dwg.use(cell,insert=trans,fill=fill,id=iden))
1660
+
1661
+ def svgFrame(self,dwg,g,cell,geom,color):
1662
+ trans = geom.translate(self._row,self._column)
1663
+ iden = f'cell_fg_{self._column:03d}{self._row:03d}'
1664
+ g.add(dwg.use(cell,
1665
+ insert=trans,
1666
+ stroke=GBXDraw.hex(color),
1667
+ fill='none',
1668
+ id=iden))
1669
+
1670
+
1671
+ def __str__(self):
1672
+ return f'({self._row:02d},{self._column:02d}): {self._tile:08x}'
1673
+
1674
+ #
1675
+ # EOF
1676
+ #
1677
+ # ====================================================================
1678
+ # From board.py
1679
+
1680
+ # --------------------------------------------------------------------
1681
+ class GBXBoardCalculator:
1682
+ def __init__(self,board):
1683
+ self._geometry = board._full
1684
+ self._nRows = board._nRows
1685
+ self._nCols = board._nCols
1686
+ self._rowOffset = board._rowOffset
1687
+ self._colOffset = board._colOffset
1688
+ self._rowInvert = board._rowInvert
1689
+ self._colInvert = board._colInvert
1690
+
1691
+ def __call__(self,x,y):
1692
+ # Shift depending on grid type and stagger
1693
+ row, col = self._geometry.inverse(x,y)
1694
+ if self._rowInvert:
1695
+ row = self._nRows - row - 1
1696
+ if self._colInvert:
1697
+ col = self._nCols - col - 1
1698
+
1699
+ return row+self._rowOffset, col+self._colOffset
1700
+
1701
+ # --------------------------------------------------------------------
1702
+ class GBXBoard:
1703
+ def __init__(self,ar):
1704
+ '''A board'''
1705
+ with VerboseGuard(f'Reading board') as g:
1706
+ self._serial = ar.iden()
1707
+ self._visible = ar.word()
1708
+ self._snap = ar.word()
1709
+ self._xSnap = ar.dword()
1710
+ self._ySnap = ar.dword()
1711
+ self._xSnapOffset = ar.dword()
1712
+ self._ySnapOffset = ar.dword()
1713
+ self._maxLayer = ar.word()
1714
+ self._background = ar.dword()
1715
+ self._name = ar.str()
1716
+ hasDraw = ar.word()
1717
+ self._baseDraw = GBXDrawList(ar) if hasDraw else None
1718
+
1719
+ self._showCellBorder = ar.word()
1720
+ self._topCellBorder = ar.word()
1721
+ self._reserved = [ar.word() for _ in range(4)]
1722
+ self._reserved2 = None
1723
+ self._rowOffset = 0
1724
+ self._colOffset = 0
1725
+ self._rowInvert = False
1726
+ self._colInvert = False
1727
+ self._nRows = 0
1728
+ self._nCols = 0
1729
+ self._transparent = False
1730
+ self._numbers = 0
1731
+ self._trackCell = False
1732
+ self._frameColor = 0xFF000000
1733
+ self._full = None
1734
+ self._half = None
1735
+ self._small = None
1736
+ self._map = []
1737
+ self._topDraw = None
1738
+
1739
+ hasArray = ar.word()
1740
+ if hasArray != 0:
1741
+ self._reserved2 = [ar.word() for _ in range(4)]
1742
+ self._rowOffset = ar.word()
1743
+ self._colOffset = ar.word()
1744
+ self._rowInvert = ar.word()
1745
+ self._colInvert = ar.word()
1746
+ self._nRows = ar.int(Features().sub_size)
1747
+ self._nCols = ar.int(Features().sub_size)
1748
+ self._transparent = ar.word()
1749
+ self._numbers = ar.word()
1750
+ self._trackCell = ar.word()
1751
+ self._frameColor = ar.dword()
1752
+
1753
+ self._full = GBXCellGeometry(ar)
1754
+ self._half = GBXCellGeometry(ar)
1755
+ self._small = GBXCellGeometry(ar)
1756
+
1757
+ self._map = [[GBXCell(ar,row,col) for col in range(self._nCols)]
1758
+ for row in range(self._nRows)]
1759
+
1760
+ hasDraw = ar.word()
1761
+ self._topDraw = GBXDrawList(ar) if hasDraw else None
1762
+ g(f'Board background read: {self._background:06x}, frame color: {self._frameColor:06x}')
1763
+
1764
+ def toDict(self,tileManager,markManager,strings,no,boardDigits,
1765
+ alsoMap=True):
1766
+ from io import StringIO
1767
+
1768
+ with VerboseGuard(f'Making dict of board {self._name}') as g:
1769
+ sav = f'board_{no:0{boardDigits}d}.svg'
1770
+ g(f'File to save in: {sav}')
1771
+ dct = {'name': self._name,
1772
+ 'serial': self._serial,
1773
+ 'visible': self._visible,
1774
+ 'snap': {
1775
+ 'enable': self._snap,
1776
+ 'x': {
1777
+ 'distance': self._xSnap,
1778
+ 'offset': self._xSnapOffset
1779
+ },
1780
+ 'y': {
1781
+ 'distance': self._ySnap,
1782
+ 'offset': self._ySnapOffset
1783
+ }
1784
+ },
1785
+ 'max layer': self._maxLayer,
1786
+ 'cell border': {
1787
+ 'visible': self._showCellBorder,
1788
+ 'on top layer': self._topCellBorder,
1789
+ },
1790
+ 'rows': {
1791
+ 'size': self._nRows,
1792
+ 'offset': self._rowOffset,
1793
+ 'inverted': self._rowInvert
1794
+ },
1795
+ 'columns': {
1796
+ 'size': self._nCols,
1797
+ 'offset': self._colOffset,
1798
+ 'inverted': self._colInvert
1799
+ },
1800
+ 'cells': {
1801
+ 'transparent': self._transparent,
1802
+ 'foreground': self._frameColor,
1803
+ },
1804
+ 'numbering': {
1805
+ 'order': 'V' if self._numbers % 2 == 0 else 'H',
1806
+ 'padding': self._numbers in [2,3],
1807
+ 'first': 'A' if self._numbers in [4,5] else 'N'
1808
+ }
1809
+ }
1810
+ if self._full is not None:
1811
+ dct['cells']['geometry'] = self._full.toDict()
1812
+ if alsoMap and self._map is not None:
1813
+ dct['cells']['list'] = [[c.toDict(tileManager,self._full)
1814
+ for c in row]
1815
+ for row in self._map]
1816
+
1817
+
1818
+ sav = f'board_{no:0{boardDigits}d}.svg'
1819
+ img = self.drawing(sav,
1820
+ tileManager=tileManager,
1821
+ markManager=markManager)
1822
+ # img.save(pretty=True)
1823
+
1824
+ stream = StringIO()
1825
+ img.write(stream,pretty=True)
1826
+
1827
+ dct['filename'] = sav
1828
+ dct['image'] = stream.getvalue()#img.tostring()
1829
+ dct['size'] = self._full.boardSize(self._nRows,self._nCols)
1830
+
1831
+ return dct
1832
+
1833
+ def drawing(self,sav,tileManager,markManager,*args,**kwargs):
1834
+ from svgwrite import Drawing
1835
+ from svgwrite.base import Title
1836
+ with VerboseGuard(f'Making SVG of board {self._name}') as g:
1837
+ size = self._full.boardSize(self._nRows,self._nCols)
1838
+ dwg = Drawing(filename=sav,size=size)
1839
+ frame, defMap, patMap = self.defs(dwg,tileManager,markManager)
1840
+
1841
+ # Draw background
1842
+ g(f'Board background: {self._background:06x} {GBXDraw.hex(self._background)}')
1843
+ dwg.add(Title(self._name))
1844
+ dwg.add(dwg.rect(id='background',
1845
+ insert=(0,0),
1846
+ size=size,
1847
+ fill=GBXDraw.hex(self._background)
1848
+ #f'#{self._background:06x}')
1849
+ ))
1850
+ # GBXDraw.hex(self._background)
1851
+
1852
+ g('Drawing base layer')
1853
+ bse = self.base (dwg, 0, defMap)
1854
+ g('Drawing cells')
1855
+ grd = self.cells(dwg, frame, patMap)
1856
+ if self._showCellBorder and not self._topCellBorder:
1857
+ self.borders(dwg,frame)
1858
+ g('Drawing top layer')
1859
+ top1 = self.top (dwg, 1, defMap)
1860
+ if self._showCellBorder and self._topCellBorder:
1861
+ self.borders(dwg,frame)
1862
+ top2 = self.top (dwg, 2, defMap)
1863
+
1864
+ return dwg
1865
+
1866
+ def defs(self,dwg,tileManager,markManager):
1867
+ defMap = {}
1868
+ patMap = {}
1869
+ frame = self._full.svgDef(dwg)
1870
+ defMap['cell'] = frame
1871
+
1872
+ # Get defininitions from base layer
1873
+ if self._baseDraw:
1874
+ self._baseDraw.svgDefs(dwg,
1875
+ tileManager,
1876
+ markManager,
1877
+ defMap)
1878
+ # Get definitions from cell layer
1879
+ for row in self._map:
1880
+ for cell in row:
1881
+ cell.svgDef(dwg,tileManager,patMap)
1882
+
1883
+ # Get definitions from top layer
1884
+ if self._topDraw:
1885
+ self._topDraw.svgDefs(dwg,
1886
+ tileManager,
1887
+ markManager,
1888
+ defMap)
1889
+
1890
+ return frame, defMap, patMap
1891
+
1892
+ def base(self,dwg,passNo,defMap):
1893
+ bse = dwg.add(dwg.g(id=f'base_{passNo:02d}'))
1894
+ if self._baseDraw:
1895
+ self._baseDraw.svg(dwg,bse,passNo,defMap)
1896
+
1897
+ return bse
1898
+
1899
+ def cells(self,dwg,frame,patMap):
1900
+ grd = dwg.add(dwg.g(id='grid'))
1901
+ for row in self._map:
1902
+ for cell in row:
1903
+ cell.svg(dwg,grd,frame,self._full,patMap)
1904
+
1905
+ return grd
1906
+
1907
+ def borders(self,dwg,frame):
1908
+ brd = dwg.add(dwg.g(id='borders'))
1909
+ for row in self._map:
1910
+ for cell in row:
1911
+ cell.svgFrame(dwg,brd,frame,self._full,self._frameColor)
1912
+
1913
+ return brd
1914
+
1915
+ def top(self,dwg,passNo,defMap):
1916
+ top = dwg.add(dwg.g(id=f'top_{passNo:02d}'))
1917
+ if self._topDraw:
1918
+ self._topDraw.svg (dwg,top,passNo,defMap)
1919
+
1920
+ return top
1921
+
1922
+ def __str__(self):
1923
+ return (f'GBXBoard: {self._name}\n'
1924
+ f' serial: {self._serial}\n'
1925
+ f' visible: {self._visible}\n'
1926
+ f' snap: {self._snap}\n'
1927
+ f' xSnap: {self._xSnap}\n'
1928
+ f' ySnap: {self._ySnap}\n'
1929
+ f' xSnapOffset: {self._xSnapOffset}\n'
1930
+ f' ySnapOffset: {self._ySnapOffset}\n'
1931
+ f' maxLayer: {self._maxLayer}\n'
1932
+ f' background: {self._background:08x}\n'
1933
+ f' Base draws: {self._baseDraw}\n'
1934
+ f' Show cell border: {self._showCellBorder}\n'
1935
+ f' Top cell border: {self._topCellBorder}\n'
1936
+ f' Reserved: {self._reserved}\n'
1937
+ f' Reserved2: {self._reserved}\n'
1938
+ f' Row offset: {self._rowOffset}\n'
1939
+ f' Column offset: {self._colOffset}\n'
1940
+ f' Row invert: {self._rowInvert}\n'
1941
+ f' Colunn invert: {self._colInvert}\n'
1942
+ f' # Rows: {self._nRows}\n'
1943
+ f' # Cols: {self._nCols}\n'
1944
+ f' Transparent: {self._transparent}\n'
1945
+ f' Numbers: {self._numbers}\n'
1946
+ f' Track cells: {self._trackCell}\n'
1947
+ f' Frame color: {self._frameColor:08x}\n'
1948
+ f' Full geometry: {self._full}\n'
1949
+ f' Half geometry: {self._half}\n'
1950
+ f' Small geometry: {self._small}\n'
1951
+ f' Top draws: {self._topDraw}'
1952
+ )
1953
+
1954
+
1955
+
1956
+
1957
+ # --------------------------------------------------------------------
1958
+ class GBXBoardManager(CbManager):
1959
+ def __init__(self,ar):
1960
+ '''Manager of boards'''
1961
+ with VerboseGuard(f'Reading board manager'):
1962
+ self._nextSerial = ar.iden()
1963
+ super(GBXBoardManager,self).__init__(ar)
1964
+ # print(Features().id_size)
1965
+ self._boards = self._readSub(ar,GBXBoard)
1966
+
1967
+ def __len__(self):
1968
+ return len(self._boards)
1969
+
1970
+ def bySerial(self,serial):
1971
+ for b in self._boards:
1972
+ if b._serial == serial:
1973
+ return b
1974
+
1975
+ return None
1976
+
1977
+ def toDict(self,tileManager,markManager,strings):
1978
+ from math import log10, ceil
1979
+ with VerboseGuard(f'Making dict board manager'):
1980
+ boardDigits = int(ceil(log10(len(self)+.5)))
1981
+
1982
+ return {b._serial:
1983
+ b.toDict(tileManager,markManager,strings,no,boardDigits)
1984
+ for no, b in enumerate(self._boards)}
1985
+
1986
+ def __str__(self):
1987
+ return ('GBXBoard manager:\n'
1988
+ + f' Next serial: {self._nextSerial:08x}\n'
1989
+ + super(GBXBoardManager,self).__str__()
1990
+ + self._strSub('boards',self._boards))
1991
+
1992
+ # --------------------------------------------------------------------
1993
+ class GSNGeomorphicElement:
1994
+ def __init__(self,ar):
1995
+ with VerboseGuard('Reading geomorphic element'):
1996
+ self._serial = ar.word()
1997
+
1998
+ def __str__(self):
1999
+ return f'GSNGeomorphicElement: {self._serial}'
2000
+
2001
+ # --------------------------------------------------------------------
2002
+ class GSNGeomorphicBoard:
2003
+ def __init__(self,ar):
2004
+ with VerboseGuard('Reading geomorphic board'):
2005
+ self._name = ar.str()
2006
+ self._nRows = ar.word()
2007
+ self._nCols = ar.word()
2008
+ n = ar.word()
2009
+ self._elements = [GSNGeomorphicElement(ar) for _ in range(n)]
2010
+
2011
+ def __str__(self):
2012
+ pl = '\n '.join([str(s) for s in self._elements])
2013
+ return (f'GeomorphicBoard: {self._name}\n'
2014
+ f' Size: {self._nRows}x{self._nCols}\n'
2015
+ f' Elements:\n {pl}\n')
2016
+
2017
+ # --------------------------------------------------------------------
2018
+ class GSNBoard:
2019
+ def __init__(self,ar,vers):
2020
+ with VerboseGuard(f'Reading scenario board {vers//256}.{vers%256}'):
2021
+ hasGeo = ar.byte()
2022
+ self._geo = GSNGeomorphicBoard(ar) if hasGeo else None
2023
+ self._serial = ar.iden()
2024
+ self._snap = ar.word()
2025
+ self._xSnap = ar.dword()
2026
+ self._ySnap = ar.dword()
2027
+ self._xSnapOffset = ar.dword()
2028
+ self._ySnapOffset = ar.dword()
2029
+ self._xStagger = ar.word()
2030
+ self._yStagger = ar.word()
2031
+ self._piecesVisible = ar.word()
2032
+ self._blockBeneath = ar.word()
2033
+ self._rotate180 = ar.word()
2034
+ self._showTiny = ar.word()
2035
+ self._indicatorsVisible= ar.word()
2036
+ self._cellBorders = ar.word()
2037
+ self._smallCellBorders = ar.word()
2038
+ self._enforceLocks = ar.word()
2039
+ self._plotLineColor = ar.dword()
2040
+ self._plotLineWidth = ar.word()
2041
+ self._lineColor = ar.dword()
2042
+ self._lineWidth = ar.word()
2043
+ self._textColor = ar.dword()
2044
+ self._textBoxColor = ar.dword()
2045
+ self._font = CbFont(ar)
2046
+ self._gridCenters = ar.word()
2047
+ self._snapMove = ar.word()
2048
+ self._indactorsTop = ar.word()
2049
+ self._openOnLoad = ar.word()
2050
+ self._prevPlotMode = ar.word()
2051
+ self._prevPlotX = ar.word()
2052
+ self._prevPlotY = ar.word()
2053
+ self._ownerMask = (ar.word() if vers < num_version(3,10) else
2054
+ ar.dword())
2055
+ self._restrictToOwner = ar.word()
2056
+ self._pieces = GBXDrawList(ar)
2057
+ self._indicators = GBXDrawList(ar)
2058
+
2059
+
2060
+ def toDict(self,boardManager):
2061
+ board = (None if boardManager is None else
2062
+ boardManager.bySerial(self._serial))
2063
+ geom = None if board is None else board._full
2064
+ calc = None if geom is None else GBXBoardCalculator(board)
2065
+
2066
+ return {
2067
+ 'onload': self._openOnLoad != 0,
2068
+ 'snap': {
2069
+ 'enable': self._snap,
2070
+ 'onmove': self._snapMove != 0,
2071
+ 'gridCenter': self._gridCenters != 0,
2072
+ 'x': {
2073
+ 'distance': self._xSnap,
2074
+ 'offset': self._xSnapOffset
2075
+ },
2076
+ 'y': {
2077
+ 'distance': self._ySnap,
2078
+ 'offset': self._ySnapOffset
2079
+ }
2080
+ },
2081
+ 'moves': {
2082
+ 'color': self._plotLineColor,
2083
+ 'width': self._plotLineWidth
2084
+ },
2085
+ 'stacking': [self._xStagger, self._yStagger],
2086
+ 'owner': self._ownerMask,
2087
+ 'restrict': self._restrictToOwner != 0,
2088
+ 'grid': {
2089
+ 'show': self._cellBorders != 0,
2090
+ 'color': self._lineColor,
2091
+ 'width': self._lineWidth
2092
+ },
2093
+ 'pieces': self._pieces.toDict(calc),
2094
+ 'indicators': self._indicators.toDict(calc)
2095
+ }
2096
+
2097
+ def __str__(self):
2098
+ return (f'ScenarioBoard: {self._serial}'
2099
+ f' Geomorphic: {"None" if self._geo is None else self._geo}\n'
2100
+ f' Font: {self._font}\n'
2101
+ f' Pieces:\n{str(self._pieces)}\n'
2102
+ f' Indicators:\n{str(self._indicators)}')
2103
+
2104
+ # --------------------------------------------------------------------
2105
+ class GSNBoardManager:
2106
+ def __init__(self,ar,vers):
2107
+ with VerboseGuard(f'Reading scenario board manager') as g:
2108
+ self._nextGeoSerial = ar.iden()
2109
+ self._reserved = [ar.word() for _ in range(3)]
2110
+ n = ar.sub_size()
2111
+ g(f'Got {n} boards to read')
2112
+ self._boards = [GSNBoard(ar,vers) for _ in range(n)]
2113
+
2114
+ def toDict(self,boardManager):
2115
+ hasStart = False
2116
+ for b in self._boards:
2117
+ if b._openOnLoad:
2118
+ hasStart = True
2119
+
2120
+ # Make sure at least one map is loaded
2121
+ if not hasStart and len(self._boards) > 0:
2122
+ self._boards[0]._openOnLoad = True
2123
+
2124
+ return {b._serial: b.toDict(boardManager) for b in self._boards }
2125
+
2126
+ def __str__(self):
2127
+ pl = '\n '.join([str(s) for s in self._boards])
2128
+ return f'GSNBoardManager: {self._nextGeoSerial}\n {pl}\n'
2129
+
2130
+ #
2131
+ # EOF
2132
+ #
2133
+
2134
+ # ====================================================================
2135
+ # From gamebox.py
2136
+
2137
+ # --------------------------------------------------------------------
2138
+ class GBXInfo:
2139
+ def __init__(self,ar):
2140
+ '''GameBox information'''
2141
+ with VerboseGuard('Reading information') as g:
2142
+ self._bitsPerPixel = ar.word() # 2 -> 2
2143
+ self._majorRevs = ar.dword() # 4 -> 6
2144
+ self._minorRevs = ar.dword() # 4 -> 10
2145
+ self._gameID = ar.dword() # 4 -> 14
2146
+ self._boxID = ar.read(16) # 16 -> 30
2147
+ self._author = ar.str() # X -> 30+X
2148
+ self._title = ar.str() # Y -> 30+X+Y
2149
+ self._description = ar.str() # Z -> 30+X+Y+Z
2150
+ self._password = ar.read(16) # 16 -> 46+X+Y+Z
2151
+ self._stickyDraw = ar.word() # 2 -> 48+X+Y+Z
2152
+ self._compression = ar.word() # 2 -> 50+X+Y+Z
2153
+ self._reserved = [ar.word() for _ in range(4)] # 4x2 -> 58+X+Y+Z
2154
+ g(f'GameBox is {self._title} by {self._author} (password: {self._password})')
2155
+
2156
+ def __str__(self):
2157
+ return ('Information:\n'
2158
+ f' Bits/pixel: {self._bitsPerPixel}\n'
2159
+ f' Major revision: {self._majorRevs}\n'
2160
+ f' Minor revision: {self._minorRevs}\n'
2161
+ f' Game ID: {self._gameID}\n'
2162
+ f' Box ID: {self._boxID}\n'
2163
+ f' Author: {self._author}\n'
2164
+ f' Title: {self._title}\n'
2165
+ f' Description: {self._description}\n'
2166
+ f' Password: {self._password}\n'
2167
+ f' Sticky Draw tools: {self._stickyDraw}\n'
2168
+ f' Compression level: {self._compression}\n'
2169
+ f' Reserved: {self._reserved}')
2170
+
2171
+
2172
+ # ====================================================================
2173
+ class GameBox:
2174
+ def __init__(self,ar):
2175
+ '''Container of game'''
2176
+ with VerboseGuard(f'Reading gamebox'):
2177
+ self._header = GBXHeader(ar,GBXHeader.BOX)
2178
+ self._info = GBXInfo(ar)
2179
+ self._strings = GBXStrings(ar)
2180
+ self._tileManager = GBXTileManager(ar)
2181
+ self._boardManager = GBXBoardManager(ar)
2182
+ self._pieceManager = GBXPieceManager(ar)
2183
+ self._markManager = GBXMarkManager(ar)
2184
+
2185
+ # print(self._strings)
2186
+ # print(self._markManager)
2187
+
2188
+ def __str__(self):
2189
+ return (str(self._header)+
2190
+ str(self._info)+
2191
+ str(self._strings)+
2192
+ str(self._tileManager)+
2193
+ str(self._boardManager)+
2194
+ str(self._pieceManager)+
2195
+ str(self._markManager))
2196
+
2197
+
2198
+ @classmethod
2199
+ def fromFile(cls,filename):
2200
+ with VerboseGuard(f'Read gamebox from {filename}'):
2201
+ with Archive(filename,'rb') as ar:
2202
+ return GameBox(ar)
2203
+
2204
+ #
2205
+ # EOF
2206
+ #
2207
+ # ====================================================================
2208
+ # From scenario.py
2209
+
2210
+ class GSNInfo:
2211
+ def __init__(self,ar):
2212
+ '''Scenario information'''
2213
+ self._disableOwnerTips = ar.word()
2214
+ self._reserved = [ar.word() for _ in range(3)]
2215
+ self._gameID = ar.dword() # 4 -> 14
2216
+ self._majorRevs = ar.dword() # 4 -> 6
2217
+ self._minorRevs = ar.dword() # 4 -> 10
2218
+ self._gbxFilename = ar.str()
2219
+ self._scenarioID = ar.dword()
2220
+ self._title = ar.str() # Y -> 30+X+Y
2221
+ self._author = ar.str() # X -> 30+X
2222
+ self._description = ar.str() # Z -> 30+X+Y+Z
2223
+ self._keepBackup = ar.word()
2224
+ self._keepHistory = ar.word()
2225
+ self._verifyState = ar.word()
2226
+ self._verifySave = ar.word()
2227
+ self._showObjectTip = ar.word()
2228
+
2229
+ def __str__(self):
2230
+ return ('Information:\n'
2231
+ f' Disable owner tips: {self._disableOwnerTips}\n'
2232
+ f' Reserved: {self._reserved}\n'
2233
+ f' Game ID: {self._gameID}\n'
2234
+ f' Major revision: {self._majorRevs}\n'
2235
+ f' Minor revision: {self._minorRevs}\n'
2236
+ f' Game box filename: {self._gbxFilename}\n'
2237
+ f' Scenario ID: {self._scenarioID}\n'
2238
+ f' Title: {self._title}\n'
2239
+ f' Author: {self._author}\n'
2240
+ f' Description: {self._description}\n'
2241
+ f' Keep backup: {self._keepBackup}\n'
2242
+ f' Keep history: {self._keepHistory}\n'
2243
+ f' Verify state: {self._verifyState}\n'
2244
+ f' Verify save: {self._verifySave}\n'
2245
+ f' Show object tips: {self._showObjectTip}\n')
2246
+
2247
+ # ====================================================================
2248
+ class Scenario:
2249
+ def __init__(self,ar,gbxfilename=None):
2250
+ '''Container of game'''
2251
+ with VerboseGuard(f'Reading scenario'):
2252
+ self._header = GBXHeader (ar,GBXHeader.SCENARIO)
2253
+ self._info = GSNInfo (ar)
2254
+ self._strings = GSNStrings (ar)
2255
+ self._playerManager = GSNPlayerManager(ar)
2256
+ self._windows = GSNWindows (ar)
2257
+ self._trayA = GSNTrayPalette (ar,self._header._vers,'A')
2258
+ self._trayB = GSNTrayPalette (ar,self._header._vers,'B')
2259
+ self._mark = GSNMarkPalette (ar,self._header._vers)
2260
+ self._boards = GSNBoardManager (ar,self._header._vers)
2261
+ self._trayManager = GSNTrayManager (ar,self._header._vers)
2262
+ self._pieceTable = GSNPieceTable (ar,self._header._vers)
2263
+ # Possibly override GBX file name
2264
+ if gbxfilename is not None and gbxfilename != '':
2265
+ self._info._gbxFilename = gbxfilename
2266
+
2267
+ self.readGameBox(ar)
2268
+
2269
+ def readGameBox(self,ar):
2270
+ from pathlib import Path
2271
+ with VerboseGuard(f'Read game box file {self._info._gbxFilename}') as v:
2272
+ gbxname = self._info._gbxFilename
2273
+ gbxpath = Path(gbxname)
2274
+
2275
+ if not gbxpath.exists():
2276
+ v(f'GameBox file {gbxpath} does not exist')
2277
+ if '\\' in gbxname:
2278
+ gbxname = gbxname.replace('\\','/')
2279
+ gbxpath = Path(gbxname)
2280
+ gbxpath = ar.path.parent / Path(gbxpath.name)
2281
+ if not gbxpath.exists():
2282
+ raise RuntimeError(f'GameBox file {gbxpath} cannot be found')
2283
+
2284
+ self._gbx = GameBox.fromFile(str(gbxpath))
2285
+
2286
+ if self._gbx._info._gameID != self._info._gameID:
2287
+ raise RuntimeError(f'Game IDs from GBX and GSN does not match: '
2288
+ f'{self._gbx._info._gameID} versus '
2289
+ f'{self._header._gameID}')
2290
+
2291
+ def __str__(self):
2292
+ return (str(self._header)+
2293
+ str(self._info)+
2294
+ str(self._strings)+
2295
+ str(self._playerManager)+
2296
+ str(self._windows)+
2297
+ str(self._trayA)+
2298
+ str(self._trayB)+
2299
+ str(self._mark)+
2300
+ str(self._boards)+
2301
+ str(self._trayManager)+
2302
+ str(self._pieceTable)+
2303
+ str(self._gbx)+
2304
+ ''
2305
+ )
2306
+
2307
+
2308
+ @classmethod
2309
+ def fromFile(cls,filename,gbxfile=None):
2310
+ with Archive(filename,'rb') as ar:
2311
+ return Scenario(ar,gbxfile)
2312
+ # ====================================================================
2313
+ # From player.py
2314
+
2315
+ class GSNPlayerManager:
2316
+ def __init__(self,ar):
2317
+ with VerboseGuard(f'Reading players mappings'):
2318
+ self._enable = ar.byte()
2319
+ self._players = []
2320
+ if self._enable:
2321
+ n = ar.sub_size()
2322
+ self._players = [GSNPlayer(ar) for _ in range(n)]
2323
+
2324
+ def toDict(self):
2325
+ return [p._name for p in self._players]
2326
+
2327
+ def __str__(self):
2328
+ pl = '\n '.join([str(s) for s in self._players])
2329
+ return ('Players:\n'
2330
+ f' Enabled: {self._enable}\n'
2331
+ f' # players: {len(self._players)}\n {pl}\n')
2332
+
2333
+ class GSNPlayer:
2334
+ def __init__(self,ar):
2335
+ with VerboseGuard(f'Reading player'):
2336
+ self._key = ar.dword()
2337
+ self._name = ar.str()
2338
+
2339
+
2340
+ def __str__(self):
2341
+ return f'Player: {self._name} (0x{self._key:08x})'
2342
+
2343
+ #
2344
+ # EOF
2345
+ #
2346
+
2347
+ # ====================================================================
2348
+ # From windows.py
2349
+
2350
+ class GSNWindow:
2351
+ def __init__(self,ar):
2352
+ with VerboseGuard('Reading window state') as g:
2353
+ self._code = ar.word()
2354
+ self._user = ar.word()
2355
+ self._board = ar.iden()
2356
+ self._state = [ar.dword() for _ in range(10)]
2357
+ n = ar.size()
2358
+ g(f'Read {self._code} {self._user} {self._board} {self._state}')
2359
+ g(f'Reading {n} bytes at {ar.tell()}')
2360
+ self._buffer = ar.read(n)
2361
+
2362
+ def __str__(self):
2363
+ return (f'code={self._code:04x} '
2364
+ f'user={self._user:04x} '
2365
+ f'board={self._board:04x} '
2366
+ f'buffer={len(self._buffer)}')
2367
+
2368
+ class GSNWindows:
2369
+ def __init__(self,ar):
2370
+ with VerboseGuard(f'Reading window states') as g:
2371
+ self._savePositions = ar.word()
2372
+ self._enable = ar.byte()
2373
+ n = ar.size() if self._enable else 0
2374
+ g(f'Save position: {self._savePositions}, '
2375
+ f'enable {self._enable} '
2376
+ f'n={n}')
2377
+ self._states = [GSNWindow(ar) for _ in range(n)]
2378
+
2379
+ def __str__(self):
2380
+ pl = '\n '.join([str(s) for s in self._states])
2381
+ return f'Windows states ({len(self._states)})\n {pl}\n'
2382
+
2383
+
2384
+ #
2385
+ # EOF
2386
+ #
2387
+ # ====================================================================
2388
+ # From palette.py
2389
+
2390
+ class GSNPalette:
2391
+ def __init__(self,ar):
2392
+ with VerboseGuard('Reading palette'):
2393
+ self._visible = ar.word()
2394
+ self._comboIndex = ar.dword()
2395
+ self._topIndex = ar.dword()
2396
+
2397
+ class GSNTrayPalette(GSNPalette):
2398
+ def __init__(self,ar,vers,iden):
2399
+ with VerboseGuard(f'Reading scenario tray palette {iden}'):
2400
+ super(GSNTrayPalette,self).__init__(ar)
2401
+ self._iden = iden
2402
+ self._listSel = readVector(ar,lambda ar : ar.dword())
2403
+
2404
+ def __str__(self):
2405
+ return f'GSNTrayPalette: {self._comboIndex} '\
2406
+ f'{self._topIndex} {self._listSel}\n'
2407
+
2408
+ class GSNMarkPalette(GSNPalette):
2409
+ def __init__(self,ar,vers):
2410
+ with VerboseGuard(f'Reading scenario mark palette'):
2411
+ super(GSNMarkPalette,self).__init__(ar)
2412
+ self._listSel = ar.dword()
2413
+ def __str__(self):
2414
+ return f'GSNMarkPalette: {self._comboIndex} '\
2415
+ f'{self._topIndex} {self._listSel}\n'
2416
+
2417
+ #
2418
+ # EOF
2419
+ #
2420
+
2421
+ # ====================================================================
2422
+ # From tray.py
2423
+
2424
+ class GSNTraySet:
2425
+ def __init__(self,ar,vers):
2426
+ with VerboseGuard(f'Reading tray set') as g:
2427
+ self._name = ar.str()
2428
+ g(f'Tray set: {self._name}')
2429
+ self._random = (ar.word() if Features().piece_100 else 0)
2430
+ self._visibleFlags = ar.dword()
2431
+ self._ownerMask = (ar.word() if vers < num_version(3,10) else
2432
+ ar.dword())
2433
+ self._restrict = ar.word()
2434
+ self._pieces = readVector(ar,lambda ar: ar.iden())
2435
+
2436
+ def __len__(self):
2437
+ return len(self._pieces)
2438
+
2439
+ def toDict(self):
2440
+ viz = self._visibleFlags & ~0xFFFF8000
2441
+ # print(f'{self._visibleFlags:08x} -> {viz}')
2442
+ vizStr = {0: 'all',
2443
+ 1: 'owner',
2444
+ 2: 'generic',
2445
+ 3: 'none'}.get(viz,'')
2446
+ return {'name': self._name,
2447
+ 'visible': vizStr,
2448
+ 'owner': self._ownerMask,
2449
+ 'restrict': self._restrict,
2450
+ 'pieces': self._pieces }
2451
+
2452
+ def __str__(self):
2453
+ return (f'Tray set: {self._name} '
2454
+ f'[visible={self._visibleFlags},'
2455
+ f'ownerMask={self._ownerMask},'
2456
+ f'resrict={self._restrict}] '
2457
+ f'({len(self)}): {self._pieces}')
2458
+
2459
+
2460
+ class GSNTrayManager:
2461
+ def __init__(self,ar,vers,iden=''):
2462
+ with VerboseGuard(f'Reading tray {iden} manager @ {ar.tell()}') as g:
2463
+ self._iden = iden
2464
+ self._reserved = [ar.word() for _ in range(4)]
2465
+ g(f'{self._reserved}')
2466
+ self._dummy = (ar.byte() if vers >= num_version(4,0) else 0)
2467
+ self._sets = readVector(ar, lambda ar : GSNTraySet(ar,vers))
2468
+
2469
+ def __len__(self):
2470
+ return len(self._sets)
2471
+
2472
+ def toDict(self):
2473
+ return [s.toDict() for s in self._sets]
2474
+
2475
+ def __str__(self):
2476
+ pl = '\n '.join([str(s) for s in self._sets])
2477
+ return f'TrayManager: {self._iden} ({len(self)})\n {pl}\n'
2478
+
2479
+
2480
+ #
2481
+ # EOF
2482
+ #
2483
+
2484
+
2485
+
2486
+
2487
+ # ====================================================================
2488
+ # From extractor.py
2489
+
2490
+ # ====================================================================
2491
+ class CbExtractor:
2492
+ def __init__(self):
2493
+ pass
2494
+
2495
+ def save(self,filename):
2496
+ from zipfile import ZipFile
2497
+ from copy import deepcopy
2498
+
2499
+ with VerboseGuard(f'Saving to {filename}') as g:
2500
+ with ZipFile(filename,'w') as zfile:
2501
+ self._save(zfile)
2502
+
2503
+ def saveImages(self,d,zfile):
2504
+ with VerboseGuard(f'Saving images') as g:
2505
+ for serial, board in d['boards'].items():
2506
+ g(f'Saving board: {board}')
2507
+ self.saveSVG(board,zfile)
2508
+
2509
+ for piece in d['pieces']['map'].values():
2510
+ g(f'Saving piece: {piece}')
2511
+ for which in ['front','back']:
2512
+ if which not in piece:
2513
+ continue
2514
+
2515
+ side = piece[which]
2516
+ self.savePNG(side,zfile)
2517
+ piece[which] = side['filename']
2518
+
2519
+ for mark in d['marks']['map'].values():
2520
+ g(f'Saving marks: {mark}')
2521
+ self.savePNG(mark,zfile)
2522
+
2523
+ for tile in d['tiles'].values():
2524
+ g(f'Saving tile: {tile}')
2525
+ self.savePNG(tile,zfile)
2526
+
2527
+ del d['tiles']
2528
+
2529
+
2530
+ def saveSVG(self,d,zfile,removeImage=True):
2531
+ from io import StringIO
2532
+
2533
+ with VerboseGuard(f'Saving SVG') as g:
2534
+ filename = d['filename']
2535
+ image = d['image']
2536
+
2537
+ # stream = StringIO()
2538
+ # image.write(stream,pretty=True)
2539
+ g(f'Saving SVG: {image}')
2540
+ zfile.writestr(filename,image)#stream.getvalue())
2541
+
2542
+ if removeImage:
2543
+ del d['image']
2544
+
2545
+ def savePNG(self,d,zfile,removeImage=True):
2546
+ with VerboseGuard(f'Saving PNG') as g:
2547
+ filename = d['filename']
2548
+ img = d['image']
2549
+
2550
+ with zfile.open(filename,'w') as file:
2551
+ g(f'Save {img}')
2552
+ img.save(file,format='PNG')
2553
+
2554
+ if removeImage:
2555
+ del d['image']
2556
+
2557
+ def _save(self,zfile):
2558
+ pass
2559
+
2560
+
2561
+ # ====================================================================
2562
+ class GBXExtractor(CbExtractor):
2563
+ def __init__(self,gbx):
2564
+ '''Turns gambox into a more sensible structure'''
2565
+ super(GBXExtractor,self).__init__()
2566
+
2567
+ with VerboseGuard(f'Extract gamebox {gbx._info._title}') as g:
2568
+ self._d = {
2569
+ 'title': gbx._info._title,
2570
+ 'author': gbx._info._author,
2571
+ 'description': gbx._info._description.replace('\r',''),
2572
+ 'major': gbx._info._majorRevs,
2573
+ 'minor': gbx._info._minorRevs,
2574
+ 'version': f'{gbx._info._majorRevs}.{gbx._info._minorRevs}'
2575
+ }
2576
+
2577
+ self._d['pieces'] = gbx._pieceManager.toDict(gbx._tileManager,
2578
+ gbx._strings)
2579
+ self._d['marks'] = gbx._markManager.toDict(gbx._tileManager,
2580
+ gbx._strings)
2581
+ self._d['boards'] = gbx._boardManager.toDict(gbx._tileManager,
2582
+ gbx._markManager,
2583
+ gbx._strings)
2584
+ self._d['tiles'] = gbx._tileManager._toStore
2585
+
2586
+ g(f'Done rationalizing {gbx._info._title}')
2587
+
2588
+
2589
+ def _save(self,zfile):
2590
+ from pprint import pprint
2591
+ from io import StringIO
2592
+ from json import dumps
2593
+ from copy import deepcopy
2594
+
2595
+ with VerboseGuard(f'Saving {self._d["title"]} to {zfile.filename}')as g:
2596
+ d = deepcopy(self._d)
2597
+
2598
+ self.saveImages(d,zfile)
2599
+
2600
+ zfile.writestr('info.json',dumps(d,indent=2))
2601
+
2602
+ g(f'Done saving')
2603
+
2604
+ def fromZipfile(self,zipfile,d):
2605
+ pass
2606
+
2607
+ def __str__(self):
2608
+ from pprint import pformat
2609
+
2610
+ return pformat(self._d,depth=2)
2611
+
2612
+ # ====================================================================
2613
+ class GSNExtractor(CbExtractor):
2614
+ def __init__(self,gsn,zipfile=None):
2615
+ '''Turns gambox into a more sensible structure'''
2616
+ super(GSNExtractor,self).__init__()
2617
+
2618
+ if zipfile is not None:
2619
+ self.fromZipfile(zipfile)
2620
+ return
2621
+
2622
+ with VerboseGuard(f'Extract scenario {gsn._info._title}') as g:
2623
+ gbxextractor = GBXExtractor(gsn._gbx)
2624
+ self._d = {
2625
+ 'title': gsn._info._title,
2626
+ 'author': gsn._info._author,
2627
+ 'description': gsn._info._description.replace('\r',''),
2628
+ 'major': gsn._info._majorRevs,
2629
+ 'minor': gsn._info._minorRevs,
2630
+ 'version': f'{gsn._info._majorRevs}.{gsn._info._minorRevs}',
2631
+ 'gamebox': gbxextractor._d}
2632
+ self._d['players'] = gsn._playerManager.toDict()
2633
+ self._d['trays'] = gsn._trayManager.toDict()
2634
+ self._d['boards'] = gsn._boards.toDict(gsn._gbx._boardManager)
2635
+
2636
+
2637
+ def _save(self,zfile):
2638
+ from pprint import pprint
2639
+ from io import StringIO
2640
+ from json import dumps
2641
+ from copy import deepcopy
2642
+
2643
+ with VerboseGuard(f'Saving {self._d["title"]} to {zfile.filename}')as g:
2644
+ d = deepcopy(self._d)
2645
+
2646
+ self.saveImages(d['gamebox'],zfile)
2647
+
2648
+ zfile.writestr('info.json',dumps(d,indent=2))
2649
+
2650
+ g(f'Done saving')
2651
+
2652
+ def fromZipfile(self,zipfile):
2653
+ from json import loads
2654
+ from PIL import Image as PILImage
2655
+ from io import BytesIO
2656
+ from wand.image import Image as WandImage
2657
+ with VerboseGuard(f'Reading module from zip file') as v:
2658
+ self._d = loads(zipfile.read('info.json').decode())
2659
+
2660
+ newMap = {}
2661
+ for pieceSID,piece in self._d['gamebox']['pieces']['map'].items():
2662
+ pieceID = int(pieceSID)
2663
+ if pieceID in newMap:
2664
+ continue
2665
+ for which in ['front', 'back']:
2666
+ if which not in piece:
2667
+ continue
2668
+
2669
+
2670
+ fn = piece[which]
2671
+ v(f'Read image {fn}')
2672
+ bts = BytesIO(zipfile.read(fn))
2673
+ img = PILImage.open(bts)
2674
+ piece[which] = {'filename': fn,
2675
+ 'image': img,
2676
+ 'size': img.size}
2677
+
2678
+ newMap[pieceID] = piece
2679
+
2680
+ del self._d['gamebox']['pieces']['map']
2681
+ self._d['gamebox']['pieces']['map'] = newMap
2682
+
2683
+ newMap = {}
2684
+ for markSID,mark in self._d['gamebox']['marks']['map'].items():
2685
+ markID = int(markSID)
2686
+ if markID in newMap:
2687
+ continue
2688
+ fn = mark['filename']
2689
+ v(f'Read image {fn}')
2690
+ bts = BytesIO(zipfile.read(fn))
2691
+ img = PILImage.open(bts)
2692
+ dsc = mark.get('description',None)
2693
+ mark['image'] = img
2694
+ mark['size'] = img.size
2695
+ newMap[markID] = mark
2696
+
2697
+ del self._d['gamebox']['marks']['map']
2698
+ self._d['gamebox']['marks']['map'] = newMap
2699
+
2700
+ newMap = {}
2701
+ for boardSID,board in self._d['gamebox']['boards'].items():
2702
+ boardID = int(boardSID)
2703
+ if boardID in newMap:
2704
+ continue
2705
+ filename = board['filename']
2706
+ v(f'Read file {filename}')
2707
+ content = zipfile.read(filename)
2708
+ img = WandImage(blob=content)
2709
+ board['image'] = content.decode()
2710
+ board['size'] = img.size
2711
+ newMap[boardID] = board
2712
+
2713
+ # del self._d['gamebox']['boards']
2714
+ # self._d['gamebox']['boards'] = newMap
2715
+
2716
+ # print(self)
2717
+
2718
+ def __str__(self):
2719
+ from pprint import pformat
2720
+
2721
+ return pformat(self._d)#,depth=5)
2722
+
2723
+ #
2724
+ # EOF
2725
+ #
2726
+ ##
2727
+ # End of generated script
2728
+ ##