cryptopunks 1.2.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,381 @@
1
+
2
+ module Cryptopunks
3
+
4
+
5
+ class Tool
6
+ def run( args )
7
+ Toolii.run( args )
8
+ end
9
+ end
10
+
11
+
12
+
13
+ class Opts
14
+ def merge_gli_options!( options = {} )
15
+ # puts " update options:"
16
+ # puts options.inspect
17
+
18
+ @file = options[:file] if options[:file]
19
+ @outdir = options[:dir] if options[:dir]
20
+
21
+ @zoom = options[:zoom] if options[:zoom]
22
+ @offset = options[:offset] if options[:offset]
23
+
24
+ @seed = options[:seed] if options[:seed]
25
+
26
+ @verbose = true if options[:verbose] == true
27
+ end
28
+
29
+
30
+ def verbose=(boolean) # add: alias for debug ??
31
+ @verbose = boolean
32
+ end
33
+
34
+ def verbose?
35
+ return false if @verbose.nil? # default verbose/debug flag is false
36
+ @verbose == true
37
+ end
38
+
39
+ def file() @file || './punks.png'; end
40
+ def file?() @file; end ## note: let's you check if file is set (or "untouched")
41
+
42
+ def zoom() @zoom || 1; end
43
+ def zoom?() @zoom; end
44
+
45
+ def offset() @offset || 0; end
46
+ def offset?() @offset; end
47
+
48
+ def outdir() @outdir || '.'; end
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
+
57
+ end # class Opts
58
+
59
+
60
+
61
+ ## note: use gli "dsl" inside a class / namespace
62
+ class Toolii
63
+ extend GLI::App
64
+
65
+ opts = Opts.new
66
+
67
+
68
+ program_desc 'punk (or cryptopunk) command line tool'
69
+
70
+ version Cryptopunks::VERSION
71
+
72
+
73
+ desc "Zoom factor x2, x4, x8, etc."
74
+ arg_name 'ZOOM'
75
+ default_value opts.zoom
76
+ flag [:z, :zoom], type: Integer
77
+
78
+ desc "Start counting at offset"
79
+ arg_name 'NUM'
80
+ default_value opts.offset
81
+ flag [:offset], type: Integer
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
+
91
+ desc "Output directory"
92
+ arg_name 'DIR'
93
+ default_value opts.outdir
94
+ flag [:d, :dir,
95
+ :o, :out, :outdir], type: String
96
+
97
+
98
+ ### todo/check: move option to -t/--tile command only - why? why not?
99
+ desc "True Official Genuine CryptoPunks™ all-in-one composite image"
100
+ arg_name 'FILE'
101
+ default_value opts.file
102
+ flag [:f, :file], type: String
103
+
104
+
105
+
106
+ ### global option (required)
107
+ ## todo: add check that path is valid?? possible?
108
+ desc '(Debug) Show debug messages'
109
+ switch [:verbose], negatable: false ## todo: use -w for short form? check ruby interpreter if in use too?
110
+
111
+
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
+
201
+ desc "Get punk characters via image tiles from all-in-one punk series composite (#{opts.file}) - for IDs use 0 to 9999"
202
+ command [:t, :tile] do |c|
203
+ c.action do |g,o,args|
204
+
205
+ # puts "opts:"
206
+ # puts opts.inspect
207
+
208
+ puts "==> reading >#{opts.file}<..."
209
+ punks = ImageComposite.read( opts.file )
210
+
211
+
212
+ puts " setting zoom to #{opts.zoom}x" if opts.zoom != 1
213
+
214
+ ## make sure outdir exits (default is current working dir e.g. .)
215
+ FileUtils.mkdir_p( opts.outdir ) unless Dir.exist?( opts.outdir )
216
+
217
+ args.each_with_index do |arg,index|
218
+ punk_index = arg.to_i( 10 ) ## assume base 10 decimal
219
+
220
+ punk = punks[ punk_index ]
221
+
222
+ punk_name = "punk-" + "%04d" % (punk_index + opts.offset)
223
+
224
+ ## if zoom - add x2,x4 or such
225
+ if opts.zoom != 1
226
+ punk = punk.zoom( opts.zoom )
227
+ punk_name << "@#{opts.zoom}x"
228
+ end
229
+
230
+ path = "#{opts.outdir}/#{punk_name}.png"
231
+ puts "==> (#{index+1}/#{args.size}) saving punk ##{punk_index+opts.offset} to >#{path}<..."
232
+
233
+ punk.save( path )
234
+ end
235
+ puts 'Done.'
236
+ end # action
237
+ end # command tile
238
+
239
+
240
+
241
+ desc 'Generate punk characters from text attributes (from scratch / zero) via builtin punk spritesheet'
242
+ command [:g, :gen, :generate] do |c|
243
+ c.action do |g,o,args|
244
+
245
+ puts "==> generating >#{args.join( ' + ' )}<..."
246
+ punk = Image.generate( *args )
247
+
248
+ puts " setting zoom to #{opts.zoom}x" if opts.zoom != 1
249
+
250
+ ## make sure outdir exits (default is current working dir e.g. .)
251
+ FileUtils.mkdir_p( opts.outdir ) unless Dir.exist?( opts.outdir )
252
+
253
+ punk_index = 0 ## assume base 10 decimal
254
+ punk_name = "punk-" + "%04d" % (punk_index + opts.offset)
255
+
256
+ ## if zoom - add x2,x4 or such
257
+ if opts.zoom != 1
258
+ punk = punk.zoom( opts.zoom )
259
+ punk_name << "@#{opts.zoom}x"
260
+ end
261
+
262
+ path = "#{opts.outdir}/#{punk_name}.png"
263
+ puts "==> saving punk ##{punk_index+opts.offset} to >#{path}<..."
264
+
265
+ punk.save( path )
266
+ puts 'Done.'
267
+ end # action
268
+ end # command generate
269
+
270
+
271
+ desc 'Query (builtin off-chain) punk contract for punk text attributes by IDs - use 0 to 9999'
272
+ command [:q, :query] do |c|
273
+ c.action do |g,o,args|
274
+
275
+ # puts "opts:"
276
+ # puts opts.inspect
277
+
278
+ args.each_with_index do |arg,index|
279
+ punk_index = arg.to_i( 10 ) ## assume base 10 decimal
280
+
281
+ puts "==> (#{index+1}/#{args.size}) punk ##{punk_index}..."
282
+
283
+ attribute_names = CryptopunksData.punk_attributes( punk_index )
284
+ ## downcase name and change spaces to underscore
285
+ attribute_names = attribute_names.map do |name|
286
+ name.downcase.gsub( ' ', '_' )
287
+ end
288
+
289
+ print " "
290
+ print attribute_names.join( ' ' )
291
+ print "\n"
292
+ end
293
+ puts 'Done.'
294
+ end
295
+ end
296
+
297
+
298
+
299
+ desc 'List all punk archetype and attribute names from builtin punk spritesheet'
300
+ command [:l, :ls, :list] do |c|
301
+ c.action do |g,o,args|
302
+
303
+ generator = Cryptopunks.generator
304
+
305
+ puts "==> Archetypes"
306
+ generator.meta.each do |rec|
307
+ next unless rec.archetype?
308
+
309
+ print " "
310
+ print "%-30s" % "#{rec.name} / (#{rec.gender})"
311
+ print " - #{rec.type}"
312
+ print "\n"
313
+ end
314
+
315
+ puts ""
316
+ puts "==> Attributes"
317
+ generator.meta.each do |rec|
318
+ next unless rec.attribute?
319
+
320
+ print " "
321
+ print "%-30s" % "#{rec.name} / (#{rec.gender})"
322
+ print " - #{rec.type}"
323
+ print "\n"
324
+ end
325
+
326
+ puts ""
327
+ puts " See github.com/cryptopunksnotdead/punks.spritesheet for more."
328
+ puts ""
329
+
330
+ puts 'Done.'
331
+ end # action
332
+ end # command list
333
+
334
+
335
+
336
+ pre do |g,c,o,args|
337
+ opts.merge_gli_options!( g )
338
+ opts.merge_gli_options!( o )
339
+
340
+ if opts.verbose?
341
+ ## LogUtils::Logger.root.level = :debug
342
+ end
343
+
344
+ ## logger.debug "Executing #{c.name}"
345
+ true
346
+ end
347
+
348
+ post do |global,c,o,args|
349
+ ## logger.debug "Executed #{c.name}"
350
+ true
351
+ end
352
+
353
+
354
+ on_error do |e|
355
+
356
+ if opts.verbose?
357
+ puts e.backtrace
358
+ end
359
+
360
+ if e.is_a?( SystemExit )
361
+ puts
362
+ puts "*** error: system exit with status code ( #{e.status} )"
363
+ exit( e.status ) ## try exit again to make sure error code gets passed along!!!
364
+ else
365
+ puts
366
+ puts "*** error: #{e.message}"
367
+ end
368
+
369
+ ## note: was false # skip default error handling
370
+
371
+ ## note: try true - false WILL SWALLOW exit codes and such
372
+ ## - looks like it's still returning 0 (e.g. on unknown option or such)!!!!
373
+ true
374
+ end
375
+
376
+
377
+ ### exit run(ARGV) ## note: use Toolii.run( ARGV ) outside of class
378
+ end # class Toolii
379
+ end # module Cryptopunks
380
+
381
+
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Cryptopunks
4
4
 
5
- MAJOR = 1
6
- MINOR = 2
7
- PATCH = 2
5
+ MAJOR = 2
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
@@ -7,6 +7,7 @@ require 'csvreader'
7
7
  ## extra stdlibs
8
8
  require 'digest' ## move/add to pixelart upstream - why? why not?
9
9
  require 'optparse'
10
+ require 'gli' ## used for command line tool
10
11
 
11
12
 
12
13
 
@@ -22,6 +23,15 @@ end
22
23
  require 'cryptopunks/attributes'
23
24
  require 'cryptopunks/structs'
24
25
  require 'cryptopunks/composite'
26
+ ## add old backwards compatible alias
27
+
28
+ module Cryptopunks
29
+ class Image
30
+ Composite = ImageComposite
31
+ end
32
+ end
33
+
34
+
25
35
  require 'cryptopunks/dataset'
26
36
 
27
37
  require 'cryptopunks/colors'
@@ -40,117 +50,137 @@ module Cryptopunks
40
50
  end
41
51
 
42
52
  class Image
43
- def self.generate( *values )
44
- img = Cryptopunks.generator.generate( *values )
45
- ## note: unwrap inner image before passing on to c'tor (requires ChunkyPNG image for now)
46
- new( img.image )
47
- end
48
- end # class Image
49
- end # module Cryptopunks
50
-
51
-
52
-
53
-
54
-
55
-
56
- module Cryptopunks
57
- class Tool
58
- def run( args )
59
- opts = { zoom: 1,
60
- outdir: '.',
61
- file: './punks.png',
62
- offset: 0,
63
- }
64
-
65
- parser = OptionParser.new do |cmd|
66
- cmd.banner = "Usage: punk (or cryptopunk) [options] IDs"
67
-
68
- cmd.separator " Mint punk characters from composite (#{opts[:file]}) - for IDs use 0 to 9999"
69
- cmd.separator ""
70
- cmd.separator " Options:"
71
-
72
- cmd.on("-z", "--zoom=ZOOM", "Zoom factor x2, x4, x8, etc. (default: #{opts[:zoom]})", Integer ) do |zoom|
73
- opts[:zoom] = zoom
74
- end
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
75
67
 
76
- cmd.on("-d", "--dir=DIR", "Output directory (default: #{opts[:outdir]})", String ) do |outdir|
77
- opts[:outdir] = outdir
78
- end
79
68
 
80
- cmd.on("-f", "--file=FILE", "True Official Genuine CryptoPunks™ composite image (default: #{opts[:file]})", String ) do |file|
81
- opts[:file] = file
82
- end
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
83
92
 
84
- cmd.on("--offset=NUM", "Start counting at offset (default: #{opts[:offset]})", Integer ) do |offset|
85
- opts[:offset] = offset
86
- end
87
93
 
88
- cmd.on("-h", "--help", "Prints this help") do
89
- puts cmd
90
- exit
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
91
126
  end
92
127
  end
93
128
 
94
- parser.parse!( args )
95
-
96
- puts "opts:"
97
- pp opts
98
-
99
- puts "==> reading >#{opts[:file]}<..."
100
- punks = Image::Composite.read( opts[:file] )
129
+ img = Cryptopunks.generator.generate( *values, style: style )
130
+ ## note: unwrap inner image before passing on to c'tor (requires ChunkyPNG image for now)
131
+ new( img.image )
132
+ end # method Image.generate
101
133
 
134
+ end # class Image
102
135
 
103
- puts " setting zoom to #{opts[:zoom]}x" if opts[:zoom] != 1
104
136
 
105
- ## make sure outdir exits (default is current working dir e.g. .)
106
- FileUtils.mkdir_p( opts[:outdir] ) unless Dir.exist?( opts[:outdir] )
137
+ class Spritesheet
138
+ ## note: for now class used for "namespace" only
139
+ def self.find_by( name:, gender: nil, size: nil ) ## return archetype/attribute image by name
140
+ # note: pass along name as q (query string)
141
+ Cryptopunks.generator.find( name,
142
+ gender: gender,
143
+ size: size )
144
+ end
145
+ end # class Spritesheet
146
+ ## add convenience (alternate spelling) alias - why? why not?
147
+ SpriteSheet = Spritesheet
148
+ Sheet = Spritesheet
149
+ Sprite = Spritesheet
150
+ end # module Cryptopunks
107
151
 
108
- args.each_with_index do |arg,index|
109
- punk_index = arg.to_i
110
152
 
111
- punk = punks[ punk_index ]
112
153
 
113
- punk_name = "punk-" + "%04d" % (punk_index + opts[:offset])
154
+ ####
155
+ # add CryptoPunksData (off-chain) contract
156
+ require 'cryptopunks/contract/punksdata-assets'
157
+ require 'cryptopunks/contract/punksdata-meta'
158
+ require 'cryptopunks/contract/punksdata-contract'
159
+ CryptoPunksData = CryptopunksData
160
+ PunksData = CryptopunksData
114
161
 
115
- ## if zoom - add x2,x4 or such
116
- if opts[:zoom] != 1
117
- punk = punk.zoom( opts[:zoom] )
118
- punk_name << "x#{opts[:zoom]}"
119
- end
120
162
 
121
- path = "#{opts[:outdir]}/#{punk_name}.png"
122
- puts "==> (#{index+1}/#{args.size}) minting punk ##{punk_index+opts[:offset]}; writing to >#{path}<..."
123
163
 
124
- punk.save( path )
125
- end
126
164
 
127
- puts "done"
128
- end ## method run
129
- end # class Tool
130
165
 
166
+ require 'cryptopunks/tool'
131
167
 
132
- def self.main( args=ARGV )
133
- Tool.new.run( args )
134
- end
168
+ ### add tool convenience helper
169
+ module Cryptopunks
170
+ def self.main( args=ARGV ) Tool.new.run( args ); end
135
171
  end ## module Cryptopunks
136
172
 
137
173
 
138
174
 
139
175
 
140
- ### add more built-in (load on demand) design series / collections
141
- DESIGNS_ORIGINAL = Cryptopunks::DesignSeries.new( "#{Cryptopunks.root}/config/original" )
142
- DESIGNS_MORE = Cryptopunks::DesignSeries.new( "#{Cryptopunks.root}/config/more" )
143
-
144
- ## all designs in one collections
145
- DESIGNS = {}.merge( DESIGNS_ORIGINAL.to_h,
146
- DESIGNS_MORE.to_h )
147
-
148
-
149
-
150
176
 
151
177
  ### add some convenience shortcuts
152
178
  CryptoPunks = Cryptopunks
153
179
  Punks = Cryptopunks
180
+ ## add singular too -why? why not?
181
+ Cryptopunk = Cryptopunks
182
+ CryptoPunk = Cryptopunks
183
+ Punk = Cryptopunks
154
184
 
155
185
 
156
186