cryptopunks 1.2.2 → 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.
@@ -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