punks 0.3.2 → 0.3.3

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.
@@ -1,337 +1,280 @@
1
-
2
- module Punk
3
-
4
- class Metadata
5
- ### todo/fix:
6
- ## move into Punks::Metadata or such
7
- class Sprite
8
- attr_reader :id, :name, :type, :gender, :size, :more_names
9
-
10
-
11
- def initialize( id:,
12
- name:,
13
- type:,
14
- gender:,
15
- size:,
16
- more_names: [] )
17
- @id = id # zero-based index eg. 0,1,2,3, etc.
18
- @name = name
19
- @type = type
20
- @gender = gender
21
- @size = size
22
- @more_names = more_names
23
- end
24
-
25
- ## todo/check - find better names for type attribute/archetypes?
26
- ## use (alternate name/alias) base or face for archetypes? any others?
27
- def attribute?() @type.downcase.start_with?( 'attribute' ); end
28
- def archetype?() @type.downcase.start_with?( 'archetype' ); end
29
-
30
- def small?() @size == 's'; end
31
- def large?() @size == 'l'; end
32
- def universal?() @size == 'u'; end
33
- alias_method :unisize?, :universal? ## add unisize or allsizes or such - why? why not?
34
-
35
- def male?() @gender == 'm'; end
36
- def female?() @gender == 'f'; end
37
- def unisex?() @gender == 'u'; end
38
- end # class Metadata::Sprite
39
- end # class Metadata
40
-
41
-
42
-
43
-
44
- class Generator
45
-
46
- ######
47
- # static helpers - (turn into "true" static self.class methods - why? why not?)
48
- #
49
- def self.normalize_key( str )
50
- ## add & e.g. B&W
51
- str.downcase.gsub(/[ ()&°_-]/, '').strip
52
- end
53
-
54
- def self.normalize_gender( str )
55
- ## e.g. Female => f
56
- ## F => f
57
- ## always return f/m
58
- str.downcase[0]
59
- end
60
-
61
- def self.normalize_size( str )
62
- ## e.g. U or Unisize or Univeral => u
63
- ## S or Small => s
64
- ## L or Large => l
65
- ## always return u/l/s
66
- str.downcase[0]
67
- end
68
-
69
- def self.normalize_name( str )
70
- ## normalize spaces in more names
71
- str.strip.gsub( /[ ]{2,}/, ' ' )
72
- end
73
-
74
- def normalize_key( str ) self.class.normalize_key( str ); end
75
- def normalize_gender( str ) self.class.normalize_gender( str ); end
76
- def normalize_size( str ) self.class.normalize_size( str ); end
77
- def normalize_name( str ) self.class.normalize_name( str ); end
78
-
79
-
80
-
81
-
82
- def build_attributes_by_name( recs )
83
- h = {}
84
- recs.each_with_index do |rec|
85
- names = [rec.name] + rec.more_names
86
-
87
- names.each do |name|
88
- key = normalize_key( name )
89
- key << "_(#{rec.gender}+#{rec.size})" if rec.attribute?
90
-
91
- if h[ key ]
92
- puts "!!! ERROR - attribute name is not unique:"
93
- pp rec
94
- puts "duplicate:"
95
- pp h[key]
96
- exit 1
97
- end
98
- h[ key ] = rec
99
- end
100
- end
101
- ## pp h
102
- h
103
- end
104
-
105
-
106
- def build_recs( recs ) ## build and normalize (meta data) records
107
-
108
- ## sort by id
109
- recs = recs.sort do |l,r|
110
- l['id'].to_i( 10 ) <=> r['id'].to_i( 10 ) # use base10 (decimal)
111
- end
112
-
113
- ## assert all recs are in order by id (0 to size)
114
- recs.each_with_index do |rec, exp_id|
115
- id = rec['id'].to_i(10)
116
- if id != exp_id
117
- puts "!! ERROR - meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
118
- exit 1
119
- end
120
- end
121
-
122
- ## convert to "wrapped / immutable" kind-of struct
123
- recs = recs.map do |rec|
124
- id = rec['id'].to_i( 10 )
125
- name = normalize_name( rec['name'] )
126
- gender = normalize_gender( rec['gender'] )
127
- size = normalize_size( rec['size'] )
128
- type = rec['type']
129
-
130
- more_names = (rec['more_names'] || '').split( '|' )
131
- more_names = more_names.map {|str| normalize_name( str ) }
132
-
133
- Metadata::Sprite.new(
134
- id: id,
135
- name: name,
136
- type: type,
137
- gender: gender,
138
- size: size,
139
- more_names: more_names )
140
- end
141
- recs
142
- end # method build_recs
143
-
144
-
145
-
146
-
147
- def initialize( image_path="./spritesheet.png",
148
- meta_path="./spritesheet.csv",
149
- image_class: )
150
- @image_class = image_class
151
-
152
- @sheet = Pixelart::ImageComposite.read( image_path,
153
- width: 24,
154
- height: 24 )
155
- recs = CsvHash.read( meta_path )
156
-
157
- @recs = build_recs( recs )
158
-
159
- ## lookup by "case/space-insensitive" name / key
160
- @attributes_by_name = build_attributes_by_name( @recs )
161
- end
162
-
163
-
164
- def spritesheet() @sheet; end
165
- alias_method :sheet, :spritesheet
166
-
167
-
168
- def records() @recs; end
169
- alias_method :meta, :records
170
-
171
-
172
- def find_meta( q, gender: nil,
173
- size: nil,
174
- style: nil ) ## note: gender (m/f) required for attributes!!!
175
-
176
- key = normalize_key( q ) ## normalize q(uery) string/symbol
177
-
178
- keys = [] ## note allow lookup by more than one keys
179
- ## e.g. if gender set try f/m first and than try unisex as fallback
180
- if gender
181
- gender = normalize_gender( gender )
182
- ## auto-fill size if not passed in
183
- ## for f(emale) => s(mall)
184
- ## m(ale) => l(arge)
185
- size = if size.nil?
186
- gender == 'f' ? 's' : 'l'
187
- else
188
- normalize_size( size )
189
- end
190
-
191
- ###
192
- # try (auto-add) style-specific version first (fallback to "regular" if not found)
193
- if style
194
- ## for now only support natural series
195
- style_key = if style.downcase.start_with?( 'natural' )
196
- 'natural'
197
- else
198
- puts "!! ERROR - unknown attribute style #{style}; sorry"
199
- exit 1
200
- end
201
-
202
- keys << "#{key}#{style_key}_(#{gender}+#{size})"
203
- ## auto-add (u)niversal size as fallback
204
- keys << "#{key}#{style_key}_(#{gender}+u)" if size == 's' || size == 'l'
205
- ## auto-add u(nisex) as fallback
206
- keys << "#{key}#{style_key}_(u+#{size})" if gender == 'f' || gender == 'm'
207
- end
208
-
209
-
210
- keys << "#{key}_(#{gender}+#{size})"
211
- ## auto-add (u)niversal size as fallback
212
- keys << "#{key}_(#{gender}+u)" if size == 's' || size == 'l'
213
- ## auto-add u(nisex) as fallback
214
- keys << "#{key}_(u+#{size})" if gender == 'f' || gender == 'm'
215
- else
216
- keys << key
217
- end
218
-
219
-
220
- rec = nil
221
- keys.each do |key|
222
- rec = @attributes_by_name[ key ]
223
- if rec
224
- puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender}+#{rec.size})"
225
- # pp rec
226
- break
227
- end
228
- end
229
-
230
- if rec.nil?
231
- puts "!! WARN - no lookup found for #{keys.size} key(s) >#{keys.inspect}<"
232
- end
233
-
234
- rec
235
- end
236
-
237
-
238
- def find( q, gender: nil, size: nil, style: nil ) ## gender (m/f) required for attributes!!!
239
- rec = find_meta( q, gender: gender, size: size, style: style )
240
-
241
- ## return image if record found
242
- rec ? @sheet[ rec.id ] : nil
243
- end
244
-
245
-
246
-
247
-
248
- def to_recs( *values, style: nil, patch: nil )
249
- archetype_name = values[0]
250
-
251
- if archetype_name.is_a?( Pixelart::Image )
252
- archetype = archetype_name
253
- elsif patch && img=patch[ normalize_key(archetype_name) ]
254
- archetype = img
255
- else ## assume it's a string
256
- ### todo/fix: check for nil/not found!!!!
257
- ## todo/check/fix: assert meta record returned is archetype NOT attribute!!!!
258
- archetype = find_meta( archetype_name )
259
- if archetype.nil?
260
- puts "!! ERROR - archetype >#{archetype}< not found; sorry"
261
- exit 1
262
- end
263
- end
264
-
265
- recs = [archetype]
266
-
267
- ## note: attribute lookup requires gender from archetype!!!!
268
- if archetype.is_a?( Pixelart::Image )
269
- if archetype_name.is_a?( String ) && archetype_name.downcase.index( 'female' )
270
- ## quick & dirty hack for now
271
- ## if name incl. female (automagically) switch to f(emale)/s(mall)
272
- attribute_gender = 'f'
273
- attribute_size = 's'
274
- else
275
- ### for now assume (support only)
276
- ## large & male (l/m) for "inline/patch" archetypes - why? why not?
277
- ## change male to unisex - why? why not? (note: for now unisex is not doing a backup lookup using male/female)
278
- attribute_gender = 'm'
279
- attribute_size = 'l'
280
- end
281
- else
282
- attribute_gender = archetype.gender
283
- attribute_size = archetype.size
284
- end
285
-
286
- attribute_names = values[1..-1]
287
- attribute_names.each do |attribute_name|
288
- ## note: quick hack - allow "inline" raw images for now - why? why not?
289
- ## pass through as-is
290
- if attribute_name.is_a?( Pixelart::Image )
291
- recs << attribute_name
292
- elsif patch && img=patch[ normalize_key(attribute_name) ]
293
- recs << img
294
- else
295
- rec = find_meta( attribute_name,
296
- gender: attribute_gender,
297
- size: attribute_size,
298
- style: style )
299
- if rec.nil?
300
- puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}+#{attribute_size}) not found; sorry"
301
- exit 1
302
- end
303
- recs << rec
304
- end
305
- end
306
-
307
- recs
308
- end
309
-
310
-
311
-
312
-
313
- def generate_image( *values, style: nil, patch: nil )
314
- ## check: rename patch to more/extras/foreign or ... - why? why not?
315
-
316
- recs = to_recs( *values, style: style, patch: patch )
317
-
318
- punk = @image_class.new( 24, 24 )
319
-
320
- recs.each do |rec|
321
- ## note: quick hack - allow "inline" raw images for now - why? why not?
322
- ## pass through as-is
323
- img = if rec.is_a?( Pixelart::Image )
324
- rec
325
- else
326
- @sheet[ rec.id ]
327
- end
328
- punk.compose!( img )
329
- end
330
-
331
- punk
332
- end
333
- alias_method :generate, :generate_image
334
-
335
- end # class Generator
336
-
337
- end # module Punk
1
+ ###
2
+ ## todo: find a better name for __Ex(tended)
3
+ ## SpriteEx, SpritesheetEx, GeneratorEx
4
+ ##
5
+ ## use SpritesheetPlus/Extra/V2/????? or such - why? why not?
6
+ ##
7
+ ## merge Metadata::Sprite & Metadata::SpriteEx into one - why? why not?
8
+
9
+
10
+ ###
11
+ ## move to spritesheet game for (re)use - why? why not?
12
+
13
+
14
+ module Pixelart
15
+
16
+ class Metadata
17
+ class SpriteEx
18
+ ### Extension to Sprite
19
+ ## incl. more (extra/extended) fields
20
+ ## - gender (u/f/m)
21
+ ## - size (u/l/s)
22
+
23
+ attr_reader :id, :name, :type, :gender, :size, :more_names
24
+
25
+ def initialize( id:,
26
+ name:,
27
+ type:,
28
+ gender:,
29
+ size:,
30
+ more_names: [] )
31
+ @id = id # zero-based index eg. 0,1,2,3, etc.
32
+ @name = name
33
+ @type = type
34
+ @gender = gender
35
+ @size = size
36
+ @more_names = more_names
37
+ end
38
+
39
+ ## todo/check - find better names for type attribute/archetypes?
40
+ ## use (alternate name/alias) base or face for archetypes? any others?
41
+ def attribute?() @type.downcase.start_with?( 'attribute' ); end
42
+ def archetype?() @type.downcase.start_with?( 'archetype' ); end
43
+
44
+ def small?() @size == 's'; end
45
+ def large?() @size == 'l'; end
46
+ def universal?() @size == 'u'; end
47
+ alias_method :unisize?, :universal? ## add unisize or allsizes or such - why? why not?
48
+
49
+ def male?() @gender == 'm'; end
50
+ def female?() @gender == 'f'; end
51
+ def unisex?() @gender == 'u'; end
52
+ end # class Metadata::Sprite
53
+ end # class Metadata
54
+
55
+
56
+ class SpritesheetEx
57
+
58
+ ######
59
+ # static helpers - (turn into "true" static self.class methods - why? why not?)
60
+ #
61
+ def self.normalize_key( str )
62
+ ## add & e.g. B&W
63
+ str.downcase.gsub(/[ ()&°_-]/, '').strip
64
+ end
65
+
66
+ def self.normalize_gender( str )
67
+ ## e.g. Female => f
68
+ ## F => f
69
+ ## always return f/m
70
+ str.downcase[0]
71
+ end
72
+
73
+ def self.normalize_size( str )
74
+ ## e.g. U or Unisize or Univeral => u
75
+ ## S or Small => s
76
+ ## L or Large => l
77
+ ## always return u/l/s
78
+ str.downcase[0]
79
+ end
80
+
81
+ def self.normalize_name( str )
82
+ ## normalize spaces in more names
83
+ str.strip.gsub( /[ ]{2,}/, ' ' )
84
+ end
85
+
86
+ def normalize_key( str ) self.class.normalize_key( str ); end
87
+ def normalize_gender( str ) self.class.normalize_gender( str ); end
88
+ def normalize_size( str ) self.class.normalize_size( str ); end
89
+ def normalize_name( str ) self.class.normalize_name( str ); end
90
+
91
+
92
+ def self._build_recs( recs ) ## build and normalize (meta data) records
93
+
94
+ ## sort by id
95
+ recs = recs.sort do |l,r|
96
+ l['id'].to_i( 10 ) <=> r['id'].to_i( 10 ) # use base10 (decimal)
97
+ end
98
+
99
+ ## assert all recs are in order by id (0 to size)
100
+ recs.each_with_index do |rec, exp_id|
101
+ id = rec['id'].to_i(10)
102
+ if id != exp_id
103
+ puts "!! ERROR - meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
104
+ exit 1
105
+ end
106
+ end
107
+
108
+ ## convert to "wrapped / immutable" kind-of struct
109
+ recs = recs.map do |rec|
110
+ id = rec['id'].to_i( 10 )
111
+ name = normalize_name( rec['name'] )
112
+ gender = normalize_gender( rec['gender'] )
113
+ size = normalize_size( rec['size'] )
114
+ type = rec['type']
115
+
116
+ more_names = (rec['more_names'] || '').split( '|' )
117
+ more_names = more_names.map {|str| normalize_name( str ) }
118
+
119
+ Metadata::SpriteEx.new(
120
+ id: id,
121
+ name: name,
122
+ type: type,
123
+ gender: gender,
124
+ size: size,
125
+ more_names: more_names )
126
+ end
127
+ recs
128
+ end # method _build_recs
129
+
130
+ def self.read_records( path )
131
+ recs = CsvHash.read( path )
132
+ _build_recs( recs )
133
+ end
134
+ class << self
135
+ alias_method :read_meta, :read_records
136
+ end
137
+
138
+ def self.read( image_path="./spritesheet.png",
139
+ meta_path="./spritesheet.csv",
140
+ width: 24,
141
+ height: 24)
142
+ img = ImageComposite.read( image_path, width: width, height: height )
143
+ recs = read_records( meta_path )
144
+
145
+ new( img, recs, width: width, height: height )
146
+ end
147
+
148
+
149
+
150
+ def initialize( img,
151
+ recs,
152
+ width: 24,
153
+ height: 24 )
154
+ @width = width
155
+ @height = height
156
+
157
+ ## todo: check if img is a ImageComposite or plain Image?
158
+ ## if plain Image "auto-wrap" into ImageComposite - why? why not?
159
+ @image = img
160
+ @recs = recs
161
+
162
+ ## lookup by "case/space-insensitive" name / key
163
+ @attributes_by_name = _build_attributes_by_name( @recs )
164
+ end
165
+
166
+
167
+ def image() @image; end
168
+ alias_method :composite, :image # add some more aliases/alt names - why? why not?
169
+
170
+ def records() @recs; end
171
+ alias_method :meta, :records
172
+
173
+
174
+ def find_meta_by( name:,
175
+ gender: nil,
176
+ size: nil,
177
+ style: nil,
178
+ warn: true ) ## note: gender (m/f) required for attributes!!!
179
+
180
+ key = normalize_key( name ) ## normalize name q(uery) string/symbol
181
+
182
+ keys = [] ## note allow lookup by more than one keys
183
+ ## e.g. if gender set try f/m first and than try unisex as fallback
184
+ if gender
185
+ gender = normalize_gender( gender )
186
+ ## auto-fill size if not passed in
187
+ ## for f(emale) => s(mall)
188
+ ## m(ale) => l(arge)
189
+ size = if size.nil?
190
+ gender == 'f' ? 's' : 'l'
191
+ else
192
+ normalize_size( size )
193
+ end
194
+
195
+ ###
196
+ # try (auto-add) style-specific version first (fallback to "regular" if not found)
197
+ if style
198
+ ## for now only support natural series
199
+ style_key = if style.downcase.start_with?( 'natural' )
200
+ 'natural'
201
+ else
202
+ puts "!! ERROR - unknown attribute style #{style}; sorry"
203
+ exit 1
204
+ end
205
+
206
+ keys << "#{key}#{style_key}_(#{gender}+#{size})"
207
+ ## auto-add (u)niversal size as fallback
208
+ keys << "#{key}#{style_key}_(#{gender}+u)" if size == 's' || size == 'l'
209
+ ## auto-add u(nisex) as fallback
210
+ keys << "#{key}#{style_key}_(u+#{size})" if gender == 'f' || gender == 'm'
211
+ end
212
+
213
+
214
+ keys << "#{key}_(#{gender}+#{size})"
215
+ ## auto-add (u)niversal size as fallback
216
+ keys << "#{key}_(#{gender}+u)" if size == 's' || size == 'l'
217
+ ## auto-add u(nisex) as fallback
218
+ keys << "#{key}_(u+#{size})" if gender == 'f' || gender == 'm'
219
+ else
220
+ keys << key
221
+ end
222
+
223
+
224
+ rec = nil
225
+ keys.each do |key|
226
+ rec = @attributes_by_name[ key ]
227
+ if rec
228
+ puts " lookup #{@image.tile_width}x#{@image.tile_height} >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender}+#{rec.size})"
229
+ # pp rec
230
+ break
231
+ end
232
+ end
233
+
234
+ if warn && rec.nil?
235
+ puts "!! WARN - no lookup #{@image.tile_width}x#{@image.tile_height} found for #{keys.size} key(s) >#{keys.inspect}<"
236
+ end
237
+
238
+ rec
239
+ end
240
+
241
+ def find_by( name:,
242
+ gender: nil,
243
+ size: nil,
244
+ style: nil,
245
+ warn: true ) ## gender (m/f) required for attributes!!!
246
+ rec = find_meta_by( name: name,
247
+ gender: gender, size: size, style: style,
248
+ warn: warn )
249
+
250
+ ## return image if record found
251
+ rec ? @image[ rec.id ] : nil
252
+ end
253
+
254
+ #############
255
+ # helpers
256
+ def _build_attributes_by_name( recs )
257
+ h = {}
258
+ recs.each_with_index do |rec|
259
+ names = [rec.name] + rec.more_names
260
+
261
+ names.each do |name|
262
+ key = normalize_key( name )
263
+ key << "_(#{rec.gender}+#{rec.size})" if rec.attribute?
264
+
265
+ if h[ key ]
266
+ puts "!!! ERROR - attribute name is not unique:"
267
+ pp rec
268
+ puts "duplicate:"
269
+ pp h[key]
270
+ exit 1
271
+ end
272
+ h[ key ] = rec
273
+ end
274
+ end
275
+ ## pp h
276
+ h
277
+ end
278
+
279
+ end # class SpritesheetEx
280
+ end # module Pixelart