cryptopunks 2.0.1 → 2.1.0

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
@@ -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