punks 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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