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,5 @@
1
+ #
2
+
3
+ #
4
+ # EOF
5
+ #
@@ -0,0 +1,82 @@
1
+ TARGET_DPI = 300
2
+
3
+ # --------------------------------------------------------------------
4
+ class DomInspect:
5
+ '''Utilities'''
6
+ @classmethod
7
+ def _get_attr(cls,node,name,default=None):
8
+ '''Get attribute from node, or default value'''
9
+ val = node.attributes.get(name,None)
10
+ if val is None:
11
+ return default
12
+ return val.nodeValue
13
+
14
+ @classmethod
15
+ def _find_children(cls,node,*tags):
16
+ '''Find all children of a node with specific tags'''
17
+ if len(tags) == 1:
18
+ return node.getElementsByTagName(tags[0])
19
+ return [child for child in node.childNodes
20
+ if child.nodeType != child.TEXT_NODE and
21
+ child.nodeType != child.COMMENT_NODE and
22
+ child.tagName in tags]
23
+
24
+ @classmethod
25
+ def _parse_hex(cls,txt):
26
+ '''Parse a hex number'''
27
+ if isinstance(txt,int):
28
+ return txt
29
+
30
+ if not (txt.startswith('0x') and txt.startswith('0X')) and \
31
+ any([c in txt for c in 'ABCFEFabcdef']):
32
+ txt = '0x'+txt
33
+
34
+ return int(txt,0)
35
+
36
+ @classmethod
37
+ def _parse_resolution(cls,txt):
38
+ '''Parse resolution specifier'''
39
+ if isinstance(txt,int):
40
+ return txt
41
+
42
+ return int(txt.lower().replace('dpi',''))
43
+
44
+ @classmethod
45
+ def _read_image(cls,zf,filename,resolution=None):
46
+ '''Read in an image from Zip file. Note the image is
47
+ explicitly converted to PNG
48
+
49
+ '''
50
+ if not filename:
51
+ return None
52
+
53
+ from wand.image import Image
54
+
55
+ with VerboseGuard(f'Reading in image from {filename}'):
56
+ with zf.open(filename,'r') as inp:
57
+ img = Image(file=inp,
58
+ #resolution=(resolution,resolution)
59
+ )
60
+ img.format = 'png'
61
+ return img
62
+
63
+ # --------------------------------------------------------------------
64
+ class ZTImage(DomInspect):
65
+ target_dpi = 300
66
+
67
+ def __init__(self,elem,prefix=''):
68
+ '''A stored image in the game box'''
69
+ self._image_file = self._get_attr(elem,prefix+'image-file')
70
+ self._reso = self._parse_resolution(
71
+ self._get_attr(elem,prefix+'resolution',150))
72
+
73
+
74
+ def read_image(self,zf):
75
+ self._image = self._read_image(zf,self._image_file,self._reso)
76
+
77
+ def __str__(self):
78
+ return f' Image: file={self._image_file}, resolution={self._reso}'
79
+
80
+ #
81
+ # EOF
82
+ #
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python
2
+ import sys
3
+ sys.path.append('..')
4
+
5
+ from common import Verbose, VerboseGuard
6
+ from common.collector import Collector
7
+
8
+ if __name__ == '__main__':
9
+ from argparse import ArgumentParser, FileType
10
+
11
+ ap = ArgumentParser(description='Collect to single script')
12
+ ap.add_argument('output',
13
+ type=FileType('w'),
14
+ nargs='?',
15
+ default='ztexport.py',
16
+ help='Output script name')
17
+ ap.add_argument('-v','--verbose',action='store_true',
18
+ help='Be verbose')
19
+
20
+ args = ap.parse_args()
21
+
22
+ Verbose().setVerbose(args.verbose)
23
+
24
+ c = Collector(executable=True)
25
+ c.run(args.output,
26
+ '../vassal/vassal.py',
27
+ 'base.py',
28
+ 'dicehand.py',
29
+ 'piece.py',
30
+ 'map.py',
31
+ 'scenario.py',
32
+ 'countersheet.py',
33
+ 'gamebox.py',
34
+ 'exporter.py',
35
+ 'ztexp.py')
36
+
37
+
38
+
@@ -0,0 +1,250 @@
1
+ ## BEGIN_IMPORTS
2
+ from base import DomInspect, ZTImage
3
+ from piece import ZTPiece
4
+ from common import VerboseGuard
5
+ ## END_IMPORTS
6
+
7
+ # --------------------------------------------------------------------
8
+ class ZTCounterImage(ZTImage):
9
+ def __init__(self,elem,prefix):
10
+ super().__init__(elem,prefix)
11
+ self._mask_file = self._get_attr(elem,prefix+'mask-file')
12
+
13
+ def read_image(self,zf):
14
+ from wand.image import Image
15
+
16
+ super().read_image(zf)
17
+
18
+ self._mask = self._read_image(zf,self._mask_file,self._reso)
19
+ if not self._mask:
20
+ return
21
+
22
+
23
+ # Get blue channel and make alpha mash from that
24
+ blue = self._mask.channel_images['blue']
25
+ blue.alpha_channel = 'copy'
26
+
27
+ # Make copy of input image, and turn on alpha channel for that
28
+ cp = Image(image=self._image)
29
+ cp.alpha_channel = 'set'
30
+
31
+ # Compose with alpha of blue mask
32
+ cp.composite(blue,operator='dst_in')
33
+
34
+ # Set new image (now with alpha channel)
35
+ self._orig = self._image
36
+ self._image = cp
37
+ self._blue = blue
38
+
39
+ # --------------------------------------------------------------------
40
+ class Face(DomInspect):
41
+ def __init__(self,elem,prefix=''):
42
+ with VerboseGuard(f'Got a face'):
43
+ self._left = int(self._get_attr(elem,prefix+'left', -1))
44
+ self._top = int(self._get_attr(elem,prefix+'top', -1))
45
+ self._right = int(self._get_attr(elem,prefix+'right', -1))
46
+ self._bottom = int(self._get_attr(elem,prefix+'bottom',-1))
47
+
48
+ @property
49
+ def width(self):
50
+ return self._right - self._left
51
+
52
+ @property
53
+ def height(self):
54
+ return self._bottom - self._top
55
+
56
+ def get_tiles(self,image,dx,dy,reverse):
57
+ with VerboseGuard(f'Get tiles from image {dx},{dy},{reverse}') as v:
58
+ # Check ordering - ZunTzu goes row-wise first
59
+ xl = self._left if not reverse else self._right-dx
60
+ xh = self._right if not reverse else self._left -dx
61
+ x1 = self._left
62
+ x2 = self._right
63
+ yl = self._top
64
+ yh = self._bottom
65
+ sx = dx if not reverse else -dx
66
+
67
+ def crop(image,x,y):
68
+ xx = max(x, x1)
69
+ xxx = min(x+dx,x2)
70
+ yy = max(y, yl)
71
+ yyy = min(y+dy,yh)
72
+ if xxx-xx < 0 or yyy-yy < 0:
73
+ return
74
+ return image[xx:xxx,yy:yyy],x+dx//2,y+dy//2
75
+
76
+ v(f'{x1} <= x+dx <= {x2} {yl} <= y+dy <= {yh}')
77
+ return [crop(image,x,y)
78
+ for y in range(yl, yh-dy//2, dy)
79
+ for x in range(xl, xh-sx//2, sx)
80
+ ]
81
+
82
+ def __str__(self):
83
+ return f'Face: {self._left},{self._top},{self._right},{self._bottom}'
84
+
85
+
86
+ # --------------------------------------------------------------------
87
+ class Section(DomInspect):
88
+ def __init__(self,elem,terrain):
89
+ with VerboseGuard(f'Got an image section') as v:
90
+ self._terrain = terrain
91
+ self._card = elem.tagName == 'card-section'
92
+ self._type = self._get_attr(elem,'type',0)
93
+ self._rows = int(self._get_attr(elem,'rows',1))
94
+ self._cols = int(self._get_attr(elem,'columns',1))
95
+ self._shadow = float(self._get_attr(elem,'shadow',
96
+ 0 if terrain else 20))
97
+ self._supply = int(self._get_attr(elem,'supply',1))
98
+ self._front = Face(elem,'face-' if self._card else 'front-')
99
+ self._back = Face(elem,'back-')
100
+ if self._terrain:
101
+ self._shadow = 0;
102
+
103
+ v(f'{"Terrain" if self._terrain else "Piece"} '
104
+ f'{"Card" if self._card else "Piece"} '
105
+ f'{self._type} {self._rows}x{self._cols} {self._supply}')
106
+
107
+ @property
108
+ def expected(self):
109
+ return self._rows * self._cols * self._supply
110
+
111
+ def get_tiles(self,image,face,reverse):
112
+ with VerboseGuard(f'Get tiles from face {face}') as v:
113
+ dx = int(face.width / self._cols+.5)
114
+ dy = int(face.height / self._rows+.5)
115
+ #v(f'Offsets are {dx},{dy}')
116
+ if dx <= 0 or dy <= 0:
117
+ return [(None,None,None)]*(self._cols*self._rows)
118
+
119
+ return face.get_tiles(image,dx,dy,reverse)
120
+
121
+ def make_pieces(self,
122
+ front_image,
123
+ back_image,
124
+ front_reso,
125
+ back_reso):
126
+ with VerboseGuard(f'Make pieces from images {self._supply}. '
127
+ f'Expecting {self.expected}') as v:
128
+ fronts = self.get_tiles(front_image, self._front, False)
129
+ backs = self.get_tiles(back_image, self._back, True)
130
+
131
+ fs = ZTImage.target_dpi / front_reso
132
+ bs = ZTImage.target_dpi / back_reso
133
+ #v(f'Scales are {fs} and {bs} '
134
+ # f'({ZTImage.target_dpi} {front_reso},{back_reso})')
135
+
136
+ def fixup(i,f,s):
137
+ if not i:
138
+ return
139
+
140
+ i.resize(int(f * i.width),int(f * i.height))
141
+ c = i.clone()
142
+ o = min(min(i.width,i.height) * s / 100 / 5,8)
143
+ c.shadow(50,o,0,0)
144
+ c.negate(channel='rgb')
145
+ c.composite(i,int(o/4),int(o/4))
146
+ return c
147
+
148
+ def make(f,b):
149
+ fi, fx, fy = f
150
+ bi, bx, by = b
151
+ #v(f' fx={fx} fy={fy} bx={bx} by={by}')
152
+ x = fs * fx if f else bs * bx
153
+ y = fs * fy if f else bs * by
154
+ fi = fixup(fi,fs,self._shadow)
155
+ bi = fixup(bi,bs,self._shadow)
156
+ #v(f' x={x} y={y} w={fi.width} h={fi.height}')
157
+ return [ZTPiece(fi,bi,int(x),int(y),self._terrain,self._card)
158
+ for _ in range(self._supply)]
159
+
160
+ return sum([make(f,b) for f,b in zip(fronts,backs)],[])
161
+
162
+ def __str__(self):
163
+ return (f' Section: {"terrain" if self._terrain else "piece"} '+
164
+ f'{"card" if self._card else "piece"} '+
165
+ f'{self._rows}x{self._cols} (x{self._supply}) '+
166
+ f'type={self._type} shadow={self._shadow}%'+'\n'
167
+ f' Front: '+str(self._front)+'\n'
168
+ f' Back: '+str(self._back))
169
+
170
+ # --------------------------------------------------------------------
171
+ class ZTCounterSheet(DomInspect):
172
+ def __init__(self,elem):
173
+ self._name = self._get_attr(elem,'name')
174
+ with VerboseGuard(f'Got a counter sheet "{self._name}"'):
175
+ self._terrain = elem.tagName == 'terrain-sheet'
176
+ self._name = self._get_attr(elem,'name')
177
+ self._front = ZTCounterImage(elem,'front-')
178
+ self._back = ZTCounterImage(elem,'back-')
179
+
180
+ self._sections = [self.parse_section(sc)
181
+ for sc in self._find_children(elem,
182
+ 'counter-section',
183
+ 'terrain-section')]
184
+ self._cards = [self.parse_card(cd)
185
+ for cd in self._find_children(elem,
186
+ 'card-section')]
187
+
188
+ def parse_section(self,section):
189
+ return Section(section,self._terrain)
190
+
191
+
192
+ def parse_card(self,card):
193
+ return Section(card,self._terrain)
194
+
195
+ def read_image(self,zf):
196
+ self._front.read_image(zf)
197
+ self._back .read_image(zf)
198
+
199
+ def make_pieces(self):
200
+ with VerboseGuard(f'Making pieces from counter sheet {self._name}') \
201
+ as v:
202
+ self._piece = sum([s.make_pieces(self._front._image,
203
+ self._back._image,
204
+ self._front._reso,
205
+ self._back._reso)
206
+ for s in self._sections],[])
207
+ self._card = sum([c.make_pieces(self._front._image,
208
+ self._back._image,
209
+ self._front._reso,
210
+ self._back._reso)
211
+ for c in self._cards],[])
212
+ exp_piece = sum([s.expected for s in self._sections])
213
+ exp_card = sum([s.expected for s in self._cards])
214
+ v(f'{len(self._piece)} pieces and {len(self._card)} cards, '
215
+ f'expected {exp_piece} pieces and {exp_card} cards')
216
+
217
+ for img, res in zip([self._front._image,self._back._image],
218
+ [self._front._reso, self._back._reso]):
219
+ if not img:
220
+ continue
221
+
222
+ s = ZTImage.target_dpi / res
223
+ #v(f'scale image from {img.width}x{img.height} by {s}')
224
+ img.resize(int(s*img.width+.5),
225
+ int(s*img.height+.5))
226
+
227
+ @property
228
+ def filename(self):
229
+ return f'{self._name.replace(" ","_")}.{self._front._image.format.lower()}'
230
+
231
+ @property
232
+ def size(self):
233
+ return self._front._image.width,self._front._image.height
234
+
235
+ def __str__(self):
236
+ return (f' Sheet: {self._name} '+
237
+ f'{"terrain" if self._terrain else "pieces"} '+'\n'+
238
+ f' Front: '+str(self._front)+'\n'+
239
+ f' Back: '+str(self._back)+
240
+ ('\n' if len(self._sections)>0 else '')+
241
+ '\n'.join([str(s) for s in self._sections])+
242
+ ('\n' if len(self._cards)>0 else '')+
243
+ '\n'.join([str(s) for s in self._cards])+
244
+ ('\n' if len(self._piece)>0 else '')+
245
+ '\n'.join([str(p) for p in self._piece])+
246
+ ('\n' if len(self._card)>0 else '')+
247
+ '\n'.join([str(c) for c in self._card]))
248
+ #
249
+ # EOF
250
+ #
@@ -0,0 +1,48 @@
1
+ ## BEGIN_IMPORTS
2
+ from base import DomInspect
3
+ from common import VerboseGuard
4
+ ## END_IMPORTS
5
+
6
+ # --------------------------------------------------------------------
7
+ class ZTDiceHand(DomInspect):
8
+
9
+ def __init__(self,elem,type=6,count=1):
10
+ with VerboseGuard(f'Got a dice hand') as v:
11
+ if elem is None:
12
+ self._type = type
13
+ self._dice = [
14
+ {'color': 'black',
15
+ 'pips': 'white',
16
+ 'texture': ''}] * count
17
+ return
18
+
19
+ self._type = int(self._get_attr(elem,'type','D6')
20
+ .lower().replace('d',''))
21
+ self._dice = []
22
+
23
+ # v(f'{elem.getElementsByTagName("dice")} child dice')
24
+ for dice in elem.getElementsByTagName('dice'):
25
+ count = int(self._get_attr(dice,'count',1))
26
+ colour = self._parse_hex(self._get_attr(dice,'color',
27
+ 0xFFFFFF))
28
+ pips = self._parse_hex(self._get_attr(dice,'pips',
29
+ 0x000000))
30
+ texture = self._get_attr(dice,'texture-file')
31
+
32
+ #v(f'Adding another dice: "{colour}" "{pips}" "{count}"')
33
+ self._dice.extend([{
34
+ 'color': colour,
35
+ 'pips': pips,
36
+ 'texture': texture}]*count)
37
+
38
+ # v(f'{len(self._dice)} dice of type d{self._type}')
39
+
40
+
41
+ def __str__(self):
42
+ return (f' Dice-hand: {self._type}'+
43
+ ('\n' if len(self._dice) > 0 else '')+
44
+ '\n'.join([f' {d["color"]},{d["pips"]},{d["texture"]}'
45
+ for d in self._dice]))
46
+ #
47
+ # EOF
48
+ #