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