cryptopunks 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -5,18 +5,20 @@ module Cryptopunks
5
5
  ### todo/fix:
6
6
  ## move into Punks::Metadata or such
7
7
  class Sprite
8
- attr_reader :id, :name, :type, :gender, :more_names
8
+ attr_reader :id, :name, :type, :gender, :size, :more_names
9
9
 
10
10
 
11
11
  def initialize( id:,
12
12
  name:,
13
13
  type:,
14
14
  gender:,
15
+ size:,
15
16
  more_names: [] )
16
17
  @id = id # zero-based index eg. 0,1,2,3, etc.
17
18
  @name = name
18
19
  @type = type
19
20
  @gender = gender
21
+ @size = size
20
22
  @more_names = more_names
21
23
  end
22
24
 
@@ -24,6 +26,15 @@ module Cryptopunks
24
26
  ## use (alternate name/alias) base or face for archetypes? any others?
25
27
  def attribute?() @type.downcase.start_with?( 'attribute' ); end
26
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
27
38
  end # class Metadata::Sprite
28
39
  end # class Metadata
29
40
 
@@ -35,32 +46,49 @@ module Cryptopunks
35
46
  ######
36
47
  # static helpers - (turn into "true" static self.class methods - why? why not?)
37
48
  #
38
- def normalize_key( str )
39
- str.downcase.gsub(/[ ()°_-]/, '').strip
49
+ def self.normalize_key( str )
50
+ ## add & e.g. B&W
51
+ str.downcase.gsub(/[ ()&°_-]/, '').strip
40
52
  end
41
53
 
42
- def normalize_gender( str )
54
+ def self.normalize_gender( str )
43
55
  ## e.g. Female => f
44
56
  ## F => f
45
- ## always return f or m
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
46
66
  str.downcase[0]
47
67
  end
48
68
 
49
- def normalize_name( str )
69
+ def self.normalize_name( str )
50
70
  ## normalize spaces in more names
51
71
  str.strip.gsub( /[ ]{2,}/, ' ' )
52
72
  end
53
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
+
54
82
 
55
83
 
56
84
  def build_attributes_by_name( recs )
57
85
  h = {}
58
86
  recs.each_with_index do |rec|
59
87
  names = [rec.name] + rec.more_names
60
- names.each do |name|
61
88
 
89
+ names.each do |name|
62
90
  key = normalize_key( name )
63
- key << "_(#{rec.gender})" if rec.attribute?
91
+ key << "_(#{rec.gender}+#{rec.size})" if rec.attribute?
64
92
 
65
93
  if h[ key ]
66
94
  puts "!!! ERROR - attribute name is not unique:"
@@ -98,6 +126,7 @@ module Cryptopunks
98
126
  id = rec['id'].to_i( 10 )
99
127
  name = normalize_name( rec['name'] )
100
128
  gender = normalize_gender( rec['gender'] )
129
+ size = normalize_size( rec['size'] )
101
130
  type = rec['type']
102
131
 
103
132
  more_names = (rec['more_names'] || '').split( '|' )
@@ -108,6 +137,7 @@ module Cryptopunks
108
137
  name: name,
109
138
  type: type,
110
139
  gender: gender,
140
+ size: size,
111
141
  more_names: more_names )
112
142
  end
113
143
  recs
@@ -138,24 +168,74 @@ module Cryptopunks
138
168
 
139
169
 
140
170
 
141
- def find_meta( q, gender: nil ) ## gender (m/f) required for attributes!!!
171
+ def find_meta( q, gender: nil,
172
+ size: nil,
173
+ style: nil ) ## note: gender (m/f) required for attributes!!!
142
174
 
143
175
  key = normalize_key( q ) ## normalize q(uery) string/symbol
144
- key << "_(#{normalize_gender( gender )})" if gender
145
176
 
146
- rec = @attributes_by_name[ key ]
147
- if rec
148
- puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender})"
149
- # pp rec
177
+ keys = [] ## note allow lookup by more than one keys
178
+ ## e.g. if gender set try f/m first and than try unisex as fallback
179
+ if gender
180
+ gender = normalize_gender( gender )
181
+ ## auto-fill size if not passed in
182
+ ## for f(emale) => s(mall)
183
+ ## m(ale) => l(arge)
184
+ size = if size.nil?
185
+ gender == 'f' ? 's' : 'l'
186
+ else
187
+ normalize_size( size )
188
+ end
189
+
190
+ ###
191
+ # try (auto-add) style-specific version first (fallback to "regular" if not found)
192
+ if style
193
+ ## for now only support natural series
194
+ style_key = if style.downcase.start_with?( 'natural' )
195
+ 'natural'
196
+ else
197
+ puts "!! ERROR - unknown attribute style #{style}; sorry"
198
+ exit 1
199
+ end
200
+
201
+ keys << "#{key}#{style_key}_(#{gender}+#{size})"
202
+ ## auto-add (u)niversal size as fallback
203
+ keys << "#{key}#{style_key}_(#{gender}+u)" if size == 's' || size == 'l'
204
+ ## auto-add u(nisex) as fallback
205
+ keys << "#{key}#{style_key}_(u+#{size})" if gender == 'f' || gender == 'm'
206
+ end
207
+
208
+
209
+ keys << "#{key}_(#{gender}+#{size})"
210
+ ## auto-add (u)niversal size as fallback
211
+ keys << "#{key}_(#{gender}+u)" if size == 's' || size == 'l'
212
+ ## auto-add u(nisex) as fallback
213
+ keys << "#{key}_(u+#{size})" if gender == 'f' || gender == 'm'
150
214
  else
151
- puts "!! WARN - no lookup found for key >#{key}<"
215
+ keys << key
216
+ end
217
+
218
+
219
+ rec = nil
220
+ keys.each do |key|
221
+ rec = @attributes_by_name[ key ]
222
+ if rec
223
+ puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender}+#{rec.size})"
224
+ # pp rec
225
+ break
226
+ end
152
227
  end
228
+
229
+ if rec.nil?
230
+ puts "!! WARN - no lookup found for #{keys.size} key(s) >#{keys.inspect}<"
231
+ end
232
+
153
233
  rec
154
234
  end
155
235
 
156
236
 
157
- def find( q, gender: nil ) ## gender (m/f) required for attributes!!!
158
- rec = find_meta( q, gender: gender )
237
+ def find( q, gender: nil, size: nil, style: nil ) ## gender (m/f) required for attributes!!!
238
+ rec = find_meta( q, gender: gender, size: size, style: style )
159
239
 
160
240
  ## return image if record found
161
241
  rec ? @sheet[ rec.id ] : nil
@@ -164,10 +244,11 @@ module Cryptopunks
164
244
 
165
245
 
166
246
 
167
- def to_recs( *values )
247
+ def to_recs( *values, style: nil )
168
248
  archetype_name = values[0]
169
249
 
170
250
  ### todo/fix: check for nil/not found!!!!
251
+ ## todo/check/fix: assert meta record returned is archetype NOT attribute!!!!
171
252
  archetype = find_meta( archetype_name )
172
253
  if archetype.nil?
173
254
  puts "!! ERROR - archetype >#{archetype}< not found; sorry"
@@ -179,11 +260,15 @@ module Cryptopunks
179
260
  attribute_names = values[1..-1]
180
261
  ## note: attribute lookup requires gender from archetype!!!!
181
262
  attribute_gender = archetype.gender
263
+ attribute_size = archetype.size
182
264
 
183
265
  attribute_names.each do |attribute_name|
184
- attribute = find_meta( attribute_name, gender: attribute_gender )
266
+ attribute = find_meta( attribute_name,
267
+ gender: attribute_gender,
268
+ size: attribute_size,
269
+ style: style )
185
270
  if attribute.nil?
186
- puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}) not found; sorry"
271
+ puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}+#{attribute_size}) not found; sorry"
187
272
  exit 1
188
273
  end
189
274
  recs << attribute
@@ -195,12 +280,13 @@ module Cryptopunks
195
280
 
196
281
 
197
282
 
198
- def generate_image( *values, background: nil )
283
+ def generate_image( *values, style: nil,
284
+ background: nil )
199
285
 
200
286
  ids = if values[0].is_a?( Integer ) ## assume integer number (indexes)
201
287
  values
202
288
  else ## assume strings (names)
203
- to_recs( *values ).map { |rec| rec.id }
289
+ to_recs( *values, style: style ).map { |rec| rec.id }
204
290
  end
205
291
 
206
292
 
@@ -1,5 +1,5 @@
1
- module Cryptopunks
2
1
 
2
+ module Cryptopunks
3
3
 
4
4
 
5
5
  class Tool
@@ -21,6 +21,8 @@ class Opts
21
21
  @zoom = options[:zoom] if options[:zoom]
22
22
  @offset = options[:offset] if options[:offset]
23
23
 
24
+ @seed = options[:seed] if options[:seed]
25
+
24
26
  @verbose = true if options[:verbose] == true
25
27
  end
26
28
 
@@ -45,6 +47,13 @@ class Opts
45
47
 
46
48
  def outdir() @outdir || '.'; end
47
49
  def outdir?() @outdir; end
50
+
51
+ ### use a standard (default) seed - why? why not?
52
+ def seed() @seed || 4142; end
53
+ def seed?() @seed; end
54
+
55
+
56
+
48
57
  end # class Opts
49
58
 
50
59
 
@@ -71,12 +80,21 @@ arg_name 'NUM'
71
80
  default_value opts.offset
72
81
  flag [:offset], type: Integer
73
82
 
83
+
84
+ desc "Seed for random number generation / shuffle"
85
+ arg_name 'NUM'
86
+ default_value opts.seed
87
+ flag [:seed], type: Integer
88
+
89
+
90
+
74
91
  desc "Output directory"
75
92
  arg_name 'DIR'
76
93
  default_value opts.outdir
77
94
  flag [:d, :dir,
78
95
  :o, :out, :outdir], type: String
79
96
 
97
+
80
98
  ### todo/check: move option to -t/--tile command only - why? why not?
81
99
  desc "True Official Genuine CryptoPunks™ all-in-one composite image"
82
100
  arg_name 'FILE'
@@ -92,6 +110,94 @@ switch [:verbose], negatable: false ## todo: use -w for short form? check rub
92
110
 
93
111
 
94
112
 
113
+ desc "Flip (vertically) all punk characters in all-in-one punk series composite (#{opts.file})"
114
+ command [:f, :flip] do |c|
115
+ c.action do |g,o,args|
116
+ puts "==> reading >#{opts.file}<..."
117
+ punks = ImageComposite.read( opts.file )
118
+
119
+ ## note: for now always assume 24x24
120
+ cols = punks.width / 24
121
+ rows = punks.height / 24
122
+ tile_width = 24
123
+ tile_height = 24
124
+
125
+ phunks = ImageComposite.new( cols, rows,
126
+ width: tile_width,
127
+ height: tile_height )
128
+
129
+ punks.each do |punk|
130
+ phunks << punk.flip_vertically
131
+ end
132
+
133
+ ## make sure outdir exits (default is current working dir e.g. .)
134
+ FileUtils.mkdir_p( opts.outdir ) unless Dir.exist?( opts.outdir )
135
+
136
+ ## note: allways assume .png extension for now
137
+ basename = File.basename( opts.file, File.extname( opts.file ) )
138
+ path = "#{opts.outdir}/#{basename}-flipped.png"
139
+ puts "==> saving phunks flipped one-by-one by hand to >#{path}<..."
140
+
141
+ phunks.save( path )
142
+ puts 'Done.'
143
+ end # action
144
+ end # command flip
145
+
146
+
147
+ desc "Shuffle all punk characters (randomly) in all-in-one punk series composite (#{opts.file})"
148
+ command [:s, :shuffle] do |c|
149
+ c.action do |g,o,args|
150
+ puts "==> reading >#{opts.file}<..."
151
+ punks = ImageComposite.read( opts.file )
152
+
153
+ ## note: for now always assume 24x24
154
+ cols = punks.width / 24
155
+ rows = punks.height / 24
156
+ tile_width = 24
157
+ tile_height = 24
158
+
159
+ phunks = ImageComposite.new( cols, rows,
160
+ width: tile_width,
161
+ height: tile_height )
162
+
163
+ tiles = cols * rows
164
+ indexes = (0..tiles-1).to_a
165
+ srand( opts.seed ) ## note: for new reset **global** random seed and use (builtin) Array#shuffle
166
+ puts " using random generation number seed >#{opts.seed}< for shuffle"
167
+ indexes = indexes.shuffle
168
+
169
+ ###
170
+ # seed 4142 ends in [..., 7566, 828, 8987, 9777]
171
+ # 333 ends in [..., 6067, 9635, 973, 8172]
172
+
173
+
174
+ indexes.each_with_index do |old_index,new_index|
175
+ puts " ##{old_index} now ##{new_index}"
176
+ phunks << punks[old_index]
177
+ end
178
+
179
+ puts " all #{tiles} old index numbers (zero-based) for reference using seed #{opts.seed}:"
180
+ puts indexes.inspect
181
+
182
+ ## make sure outdir exits (default is current working dir e.g. .)
183
+ FileUtils.mkdir_p( opts.outdir ) unless Dir.exist?( opts.outdir )
184
+
185
+ ## note: allways assume .png extension for now
186
+ basename = File.basename( opts.file, File.extname( opts.file ) )
187
+ path = "#{opts.outdir}/#{basename}-#{opts.seed}.png"
188
+ puts "==> saving p(h)unks shuffled one-by-one by hand to >#{path}<..."
189
+
190
+ phunks.save( path )
191
+ puts 'Done.'
192
+ end # action
193
+ end # command shuffle
194
+
195
+
196
+
197
+
198
+
199
+
200
+
95
201
  desc "Get punk characters via image tiles from all-in-one punk series composite (#{opts.file}) - for IDs use 0 to 9999"
96
202
  command [:t, :tile] do |c|
97
203
  c.action do |g,o,args|
@@ -3,8 +3,8 @@
3
3
  module Cryptopunks
4
4
 
5
5
  MAJOR = 2
6
- MINOR = 0
7
- PATCH = 1
6
+ MINOR = 1
7
+ PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
10
10
  def self.version
data/lib/cryptopunks.rb CHANGED
@@ -50,24 +50,103 @@ module Cryptopunks
50
50
  end
51
51
 
52
52
  class Image
53
- def self.generate( *values )
54
- img = Cryptopunks.generator.generate( *values )
53
+ def self.generate( *values, style: nil )
54
+
55
+ ##### add style option / hack - why? why not?
56
+ if style
57
+ values = if style.downcase.index( 'natural') && style.downcase.index( '2')
58
+ ["#{values[0]} (N2)"] + values[1..-1]
59
+ elsif style.downcase[0] == 'n' ## starting with n - always assume natural(s)
60
+ ## auto-add (N) for Natural to archetype
61
+ ["#{values[0]} (N)"] + values[1..-1]
62
+ else
63
+ puts "!! ERROR - unknown punk style #{style}; sorry"
64
+ exit 1
65
+ end
66
+ end
67
+
68
+
69
+ ###### hack for black&white
70
+ ## auto-add b&w (black&white) to all attribute names e.g.
71
+ ## Eyes => Eyes B&W
72
+ ## Smile => Smile B&W
73
+ ## ....
74
+
75
+ archetype_key = Generator.normalize_key( values[0] )
76
+ if archetype_key.end_with?( 'bw' ) || ## e.g. B&W
77
+ archetype_key.end_with?( '1bit') ## e.g. 1-Bit or 1Bit
78
+
79
+ values = [values[0]] + values[1..-1].map do |attribute|
80
+ attribute_key = Generator.normalize_key( attribute )
81
+ if ['wildhair'].include?( attribute_key ) ## pass through known b&w attributes by "default"
82
+ attribute
83
+ elsif attribute_key.index( "black" )
84
+ attribute ## pass through as-is
85
+ else
86
+ "#{attribute} B&W"
87
+ end
88
+ end
89
+
90
+ pp values
91
+ end
92
+
93
+
94
+ # note: female mouth by default has "custom" colors (not black)
95
+ # for every 1/2/3/4 (human) skin tone and for zombie
96
+ # auto-"magically" add mapping
97
+ #
98
+ # todo/check/fix - add more "contraints"
99
+ # for mapping to only kick-in for "basic" versions
100
+ # and not "colored" e.g. golden and such - why? why not?
101
+ #
102
+ # move this mapping here to "post-lookup" processing
103
+ # to get/incl. more "meta" attribute info - why? why not?
104
+ if archetype_key.index( 'female1' ) ||
105
+ archetype_key.index( 'female2' ) ||
106
+ archetype_key.index( 'female3' ) ||
107
+ archetype_key.index( 'female4' ) ||
108
+ archetype_key.index( 'zombiefemale' )
109
+
110
+ values = [values[0]] + values[1..-1].map do |attribute|
111
+ attribute_key = Generator.normalize_key( attribute )
112
+
113
+ if attribute_key == 'smile' || attribute_key == 'frown'
114
+ attribute += if archetype_key.index( 'zombiefemale' ) then ' Zombie'
115
+ elsif archetype_key.index( 'female1' ) then ' 1'
116
+ elsif archetype_key.index( 'female2' ) then ' 2'
117
+ elsif archetype_key.index( 'female3' ) then ' 3'
118
+ elsif archetype_key.index( 'female4' ) then ' 4'
119
+ else
120
+ puts "!! ERROR - smile or frown (mouth expression) not supported for archetype:"
121
+ pp values
122
+ exit 1
123
+ end
124
+ end
125
+ attribute
126
+ end
127
+ end
128
+
129
+ img = Cryptopunks.generator.generate( *values, style: style )
55
130
  ## note: unwrap inner image before passing on to c'tor (requires ChunkyPNG image for now)
56
131
  new( img.image )
57
- end
132
+ end # method Image.generate
133
+
58
134
  end # class Image
59
135
 
60
136
 
61
137
  class Spritesheet
62
138
  ## note: for now class used for "namespace" only
63
- def self.find_by( name:, gender: nil ) ## return archetype/attribute image by name
139
+ def self.find_by( name:, gender: nil, size: nil ) ## return archetype/attribute image by name
64
140
  # note: pass along name as q (query string)
65
- Cryptopunks.generator.find( name, gender: gender )
141
+ Cryptopunks.generator.find( name,
142
+ gender: gender,
143
+ size: size )
66
144
  end
67
145
  end # class Spritesheet
68
146
  ## add convenience (alternate spelling) alias - why? why not?
69
147
  SpriteSheet = Spritesheet
70
148
  Sheet = Spritesheet
149
+ Sprite = Spritesheet
71
150
  end # module Cryptopunks
72
151
 
73
152
 
@@ -98,6 +177,12 @@ end ## module Cryptopunks
98
177
  ### add some convenience shortcuts
99
178
  CryptoPunks = Cryptopunks
100
179
  Punks = Cryptopunks
180
+ ## add singular too -why? why not?
181
+ Cryptopunk = Cryptopunks
182
+ CryptoPunk = Cryptopunks
183
+ Punk = Cryptopunks
184
+
185
+
101
186
 
102
187
 
103
188
  ###
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cryptopunks
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-17 00:00:00.000000000 Z
11
+ date: 2022-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pixelart
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.2.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 1.2.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: csvreader
29
29
  requirement: !ruby/object:Gem::Requirement