punks 0.1.0 → 0.2.1

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.
Binary file
@@ -0,0 +1,329 @@
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
+ ### for now assume (support only)
270
+ ## large & unisex (u&u) for "inline/patch" archetypes - why? why not?
271
+ attribute_gender = 'u'
272
+ attribute_size = 'l'
273
+ else
274
+ attribute_gender = archetype.gender
275
+ attribute_size = archetype.size
276
+ end
277
+
278
+ attribute_names = values[1..-1]
279
+ attribute_names.each do |attribute_name|
280
+ ## note: quick hack - allow "inline" raw images for now - why? why not?
281
+ ## pass through as-is
282
+ if attribute_name.is_a?( Pixelart::Image )
283
+ recs << attribute_name
284
+ elsif patch && img=patch[ normalize_key(attribute_name) ]
285
+ recs << img
286
+ else
287
+ rec = find_meta( attribute_name,
288
+ gender: attribute_gender,
289
+ size: attribute_size,
290
+ style: style )
291
+ if rec.nil?
292
+ puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}+#{attribute_size}) not found; sorry"
293
+ exit 1
294
+ end
295
+ recs << rec
296
+ end
297
+ end
298
+
299
+ recs
300
+ end
301
+
302
+
303
+
304
+
305
+ def generate_image( *values, style: nil, patch: nil )
306
+ ## check: rename patch to more/extras/foreign or ... - why? why not?
307
+
308
+ recs = to_recs( *values, style: style, patch: patch )
309
+
310
+ punk = @image_class.new( 24, 24 )
311
+
312
+ recs.each do |rec|
313
+ ## note: quick hack - allow "inline" raw images for now - why? why not?
314
+ ## pass through as-is
315
+ img = if rec.is_a?( Pixelart::Image )
316
+ rec
317
+ else
318
+ @sheet[ rec.id ]
319
+ end
320
+ punk.compose!( img )
321
+ end
322
+
323
+ punk
324
+ end
325
+ alias_method :generate, :generate_image
326
+
327
+ end # class Generator
328
+
329
+ end # module Punk
@@ -0,0 +1,20 @@
1
+
2
+
3
+ module Marilyn
4
+
5
+ class Image < Pixelart::Image
6
+
7
+ MARILYN_ATTRIBUTES = ['Female 3', 'Wild Blonde', 'Mole',
8
+ 'Blue Eye Shadow']
9
+
10
+ def self.generate( *values, style: nil )
11
+ punk = Punk::Image.generate( *MARILYN_ATTRIBUTES,
12
+ *values, style: style )
13
+ phunk = punk.mirror
14
+
15
+ ## wrap in Marilyn class (note: use/ requires inner image)
16
+ new( phunk.width, phunk.height, phunk.image )
17
+ end
18
+ end # class Image
19
+ end # module Marilyn
20
+
@@ -0,0 +1,24 @@
1
+
2
+
3
+ module Philip
4
+
5
+ class Image < Pixelart::Image
6
+
7
+ ## note: right-looking ("pre-phlipped") philip
8
+ PHILIP = Pixelart::Image.read( "#{Pixelart::Module::Punks.root}/config/philip-24x24.png" )
9
+
10
+ def self.generate( *values )
11
+ punk = Image.new( 24, 24 )
12
+ punk.compose!( PHILIP )
13
+
14
+ values.each do |name|
15
+ attribute = Punk::Sheet.find_by( name: name,
16
+ gender: 'm' )
17
+ punk.compose!( attribute )
18
+ end
19
+
20
+ phunk = punk.mirror
21
+ phunk
22
+ end
23
+ end # class Image
24
+ end # module Philip
@@ -0,0 +1,14 @@
1
+
2
+ module Phunk
3
+
4
+ class Image < Pixelart::Image
5
+
6
+ def self.generate( *values, style: nil )
7
+ punk = Punk::Image.generate( *values, style: style )
8
+ phunk = punk.mirror
9
+
10
+ ## wrap in Phunks class (note: use/ requires inner image)
11
+ new( phunk.width, phunk.height, phunk.image )
12
+ end
13
+ end # class Image
14
+ end # module Phunk
@@ -0,0 +1,123 @@
1
+
2
+
3
+ module Punk
4
+
5
+ class Image < Pixelart::Image
6
+ def self.generator
7
+ ### todo/fix: break out/ split off spritesheet from generator!!!!
8
+ @generator ||= Generator.new( "#{Pixelart::Module::Punks.root}/config/punks-24x24.png",
9
+ "#{Pixelart::Module::Punks.root}/config/punks-24x24.csv", image_class: Image )
10
+ end
11
+
12
+
13
+ def self.generate( *values, style: nil, patch: nil )
14
+
15
+ if values[0].is_a?( String )
16
+ ##### add style option / hack - why? why not?
17
+ if style
18
+ values = if style.downcase.index( 'natural') && style.downcase.index( '2')
19
+ ["#{values[0]} (N2)"] + values[1..-1]
20
+ elsif style.downcase[0] == 'n' ## starting with n - always assume natural(s)
21
+ ## auto-add (N) for Natural to archetype
22
+ ["#{values[0]} (N)"] + values[1..-1]
23
+ else
24
+ puts "!! ERROR - unknown punk style #{style}; sorry"
25
+ exit 1
26
+ end
27
+ end
28
+
29
+ ###### hack for black&white
30
+ ## auto-add b&w (black&white) to all attribute names e.g.
31
+ ## Eyes => Eyes B&W
32
+ ## Smile => Smile B&W
33
+ ## ....
34
+ archetype_key = Generator.normalize_key( values[0] )
35
+ if archetype_key.end_with?( 'bw' ) || ## e.g. B&W
36
+ archetype_key.end_with?( '1bit') ## e.g. 1-Bit or 1Bit
37
+
38
+ values = [values[0]] + values[1..-1].map do |attribute|
39
+ if attribute.is_a?( Pixelart::Image )
40
+ attribute
41
+ else
42
+ attribute_key = Generator.normalize_key( attribute )
43
+ if ['wildhair'].include?( attribute_key ) ## pass through known b&w attributes by "default"
44
+ attribute
45
+ elsif attribute_key.index( "black" )
46
+ attribute ## pass through as-is
47
+ else
48
+ "#{attribute} B&W"
49
+ end
50
+ end
51
+ end
52
+
53
+ pp values
54
+ end
55
+
56
+ # note: female mouth by default has "custom" colors (not black)
57
+ # for every 1/2/3/4 (human) skin tone and for zombie
58
+ # auto-"magically" add mapping
59
+ #
60
+ # todo/check/fix - add more "contraints"
61
+ # for mapping to only kick-in for "basic" versions
62
+ # and not "colored" e.g. golden and such - why? why not?
63
+ #
64
+ # move this mapping here to "post-lookup" processing
65
+ # to get/incl. more "meta" attribute info - why? why not?
66
+ if archetype_key.index( 'female1' ) ||
67
+ archetype_key.index( 'female2' ) ||
68
+ archetype_key.index( 'female3' ) ||
69
+ archetype_key.index( 'female4' ) ||
70
+ archetype_key.index( 'zombiefemale' )
71
+
72
+ values = [values[0]] + values[1..-1].map do |attribute|
73
+ if attribute.is_a?( Pixelart::Image )
74
+ attribute
75
+ else
76
+ attribute_key = Generator.normalize_key( attribute )
77
+
78
+ if attribute_key == 'smile' || attribute_key == 'frown'
79
+ attribute += if archetype_key.index( 'zombiefemale' ) then ' Zombie'
80
+ elsif archetype_key.index( 'female1' ) then ' 1'
81
+ elsif archetype_key.index( 'female2' ) then ' 2'
82
+ elsif archetype_key.index( 'female3' ) then ' 3'
83
+ elsif archetype_key.index( 'female4' ) then ' 4'
84
+ else
85
+ puts "!! ERROR - smile or frown (mouth expression) not supported for archetype:"
86
+ pp values
87
+ exit 1
88
+ end
89
+ end
90
+ attribute
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ generator.generate( *values, style: style, patch: patch )
97
+ end # method Image.generate
98
+ end # class Image
99
+
100
+
101
+
102
+ class Spritesheet
103
+ def self.builtin
104
+ ## for now get spritesheet via generator
105
+ Image.generator
106
+ end
107
+ ## note: for now class used for "namespace" only
108
+ def self.find_by( name:, gender: nil, size: nil ) ## return archetype/attribute image by name
109
+ # note: pass along name as q (query string)
110
+ builtin.find( name,
111
+ gender: gender,
112
+ size: size )
113
+ end
114
+ end # class Spritesheet
115
+ ## add convenience (alternate spelling) alias - why? why not?
116
+ SpriteSheet = Spritesheet
117
+ Sheet = Spritesheet
118
+ Sprite = Spritesheet
119
+ end # module Punk
120
+
121
+
122
+
123
+
@@ -0,0 +1,36 @@
1
+
2
+
3
+ module Punkxl
4
+
5
+ class Spritesheet
6
+ def self.builtin
7
+ @builtin ||= Pixelart::Spritesheet.read( "#{Pixelart::Module::Punks.root}/config/punks_xl-32x32.png",
8
+ "#{Pixelart::Module::Punks.root}/config/punks_xl-32x32.csv",
9
+ width: 32,
10
+ height: 32 )
11
+ end
12
+
13
+ ## note: for now class used for "namespace" only
14
+ def self.find_by( name: ) ## return archetype/attribute image by name
15
+ builtin.find_by( name: name )
16
+ end
17
+ end # class Spritesheet
18
+ ## add convenience (alternate spelling) alias - why? why not?
19
+ SpriteSheet = Spritesheet
20
+ Sheet = Spritesheet
21
+ Sprite = Spritesheet
22
+
23
+
24
+ class Image < Pixelart::Image
25
+ def self.generator
26
+ @generator ||= Artfactory.use( Punkxl::Sheet.builtin,
27
+ image_class: Image )
28
+ end
29
+ def self.generate( *names )
30
+ generator.generate( *names )
31
+ end
32
+ end # class Image
33
+
34
+
35
+ end # module Punkxl
36
+
data/lib/punks/version.rb CHANGED
@@ -4,8 +4,8 @@ module Module
4
4
  module Punks
5
5
 
6
6
  MAJOR = 0
7
- MINOR = 1
8
- PATCH = 0
7
+ MINOR = 2
8
+ PATCH = 1
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
  def self.version
data/lib/punks.rb CHANGED
@@ -1,57 +1,54 @@
1
1
  ## 3rd party
2
2
  require 'pixelart/base'
3
3
  require 'backgrounds/base'
4
- require 'artfactory' ### todo/fix: change to artfactory/base
4
+ require 'artfactory/base'
5
5
 
6
6
 
7
7
  ## our own code
8
8
  require 'punks/version' # note: let version always go first
9
9
 
10
+ require 'punks/generator'
10
11
 
11
12
 
12
- ###
13
- ## add convenience pre-configurated generatored with build-in spritesheet (see config)
14
-
15
- module Punkxl
16
-
17
- class Spritesheet
18
- def self.builtin
19
- @builtin ||= Pixelart::Spritesheet.read( "#{Pixelart::Module::Punks.root}/config/punks_xl-32x32.png",
20
- "#{Pixelart::Module::Punks.root}/config/punks_xl-32x32.csv",
21
- width: 32,
22
- height: 32 )
23
- end
13
+ ## --- 24x24 series
14
+ require 'punks/punks'
15
+ require 'punks/phunks'
16
+ require 'punks/marilyns'
17
+ require 'punks/philips'
18
+ ## --- 32x32 series
19
+ require 'punks/punks_xl'
24
20
 
25
- ## note: for now class used for "namespace" only
26
- def self.find_by( name: ) ## return archetype/attribute image by name
27
- builtin.find_by( name: name )
28
- end
29
- end # class Spritesheet
30
- ## add convenience (alternate spelling) alias - why? why not?
31
- SpriteSheet = Spritesheet
32
- Sheet = Spritesheet
33
- Sprite = Spritesheet
34
21
 
35
22
 
36
- class Image < Pixelart::Image
37
- def self.generator
38
- @generator ||= Artfactory.use( Punkxl::Sheet.builtin )
39
- end
40
- def self.generate( *names )
41
- img = generator.generate( *names )
42
- ## note: unwrap inner image before passing on to c'tor (requires ChunkyPNG image for now)
43
- new( 32, 32, img.image )
44
- end # method Image.generate
45
- end # class Image
46
23
 
47
24
 
48
- end # module Punkxl
25
+ ### add some convenience shortcuts
26
+ Cryptopunks = Punk
27
+ CryptoPunks = Punk
28
+ Punks = Punk
29
+ ## add singular too -why? why not?
30
+ Cryptopunk = Punk
31
+ CryptoPunk = Punk
49
32
 
50
33
 
51
34
  ### add some convenience shortcuts / alternate spelling variants
52
35
  PunkXL = Punkxl
53
36
  PunkXl = Punkxl
54
37
 
38
+ ### add some convenience shortcuts / alternate spelling variants
39
+ Phunks = Phunk
40
+
41
+ ### add some convenience shortcuts / alternate spelling variants
42
+ Philips = Philip
43
+
44
+ ### add some convenience shortcuts / alternate spelling variants
45
+ Marilyns = Marilyn
46
+
47
+
48
+
49
+ ###
50
+ # note: for convenience auto include Pixelart namespace!!! - why? why not?
51
+ include Pixelart
55
52
 
56
53
 
57
54