cryptopunks 1.2.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +4 -20
- data/README.md +416 -34
- data/Rakefile +2 -1
- data/config/spritesheet.csv +762 -237
- data/config/spritesheet.png +0 -0
- data/lib/cryptopunks/composite.rb +3 -4
- data/lib/cryptopunks/contract/punksdata-assets.rb +338 -0
- data/lib/cryptopunks/contract/punksdata-contract.rb +55 -0
- data/lib/cryptopunks/contract/punksdata-meta.rb +2107 -0
- data/lib/cryptopunks/generator.rb +148 -43
- data/lib/cryptopunks/image.rb +4 -92
- data/lib/cryptopunks/tool.rb +381 -0
- data/lib/cryptopunks/version.rb +3 -3
- data/lib/cryptopunks.rb +113 -83
- metadata +21 -43
- data/config/more/alien-female.txt +0 -34
- data/config/more/ape-female.txt +0 -33
- data/config/more/demon-female.txt +0 -33
- data/config/more/demon-male.txt +0 -33
- data/config/more/mummy-female.txt +0 -33
- data/config/more/mummy-male.txt +0 -33
- data/config/more/orc-female.txt +0 -33
- data/config/more/orc-male.txt +0 -33
- data/config/more/robot-female.txt +0 -34
- data/config/more/robot-male.txt +0 -33
- data/config/more/skeleton-female.txt +0 -33
- data/config/more/skeleton-male.txt +0 -33
- data/config/more/vampire-female.txt +0 -33
- data/config/more/vampire-male.txt +0 -33
- data/config/more/zombie-female.txt +0 -33
- data/config/original/alien-male.txt +0 -33
- data/config/original/ape-male.txt +0 -33
- data/config/original/human-female.txt +0 -33
- data/config/original/human-male.txt +0 -34
- data/config/original/zombie-male.txt +0 -33
@@ -5,23 +5,36 @@ 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
|
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
|
-
gender
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
@
|
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
|
19
23
|
end
|
20
24
|
|
21
25
|
## todo/check - find better names for type attribute/archetypes?
|
22
26
|
## use (alternate name/alias) base or face for archetypes? any others?
|
23
27
|
def attribute?() @type.downcase.start_with?( 'attribute' ); end
|
24
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
|
25
38
|
end # class Metadata::Sprite
|
26
39
|
end # class Metadata
|
27
40
|
|
@@ -33,33 +46,60 @@ module Cryptopunks
|
|
33
46
|
######
|
34
47
|
# static helpers - (turn into "true" static self.class methods - why? why not?)
|
35
48
|
#
|
36
|
-
def normalize_key( str )
|
37
|
-
|
49
|
+
def self.normalize_key( str )
|
50
|
+
## add & e.g. B&W
|
51
|
+
str.downcase.gsub(/[ ()&°_-]/, '').strip
|
38
52
|
end
|
39
53
|
|
40
|
-
def normalize_gender( str )
|
54
|
+
def self.normalize_gender( str )
|
41
55
|
## e.g. Female => f
|
42
56
|
## F => f
|
43
|
-
## always return 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
|
44
66
|
str.downcase[0]
|
45
67
|
end
|
46
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
|
+
|
47
83
|
|
48
84
|
def build_attributes_by_name( recs )
|
49
85
|
h = {}
|
50
86
|
recs.each_with_index do |rec|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
87
|
+
names = [rec.name] + rec.more_names
|
88
|
+
|
89
|
+
names.each do |name|
|
90
|
+
key = normalize_key( name )
|
91
|
+
key << "_(#{rec.gender}+#{rec.size})" if rec.attribute?
|
92
|
+
|
93
|
+
if h[ key ]
|
94
|
+
puts "!!! ERROR - attribute name is not unique:"
|
95
|
+
pp rec
|
96
|
+
puts "duplicate:"
|
97
|
+
pp h[key]
|
98
|
+
exit 1
|
99
|
+
end
|
100
|
+
h[ key ] = rec
|
60
101
|
end
|
61
|
-
|
62
|
-
end
|
102
|
+
end
|
63
103
|
## pp h
|
64
104
|
h
|
65
105
|
end
|
@@ -83,16 +123,22 @@ module Cryptopunks
|
|
83
123
|
|
84
124
|
## convert to "wrapped / immutable" kind-of struct
|
85
125
|
recs = recs.map do |rec|
|
86
|
-
id
|
87
|
-
name
|
88
|
-
gender
|
89
|
-
|
126
|
+
id = rec['id'].to_i( 10 )
|
127
|
+
name = normalize_name( rec['name'] )
|
128
|
+
gender = normalize_gender( rec['gender'] )
|
129
|
+
size = normalize_size( rec['size'] )
|
130
|
+
type = rec['type']
|
131
|
+
|
132
|
+
more_names = (rec['more_names'] || '').split( '|' )
|
133
|
+
more_names = more_names.map {|str| normalize_name( str ) }
|
90
134
|
|
91
135
|
Metadata::Sprite.new(
|
92
|
-
id:
|
93
|
-
name:
|
94
|
-
type:
|
95
|
-
gender:
|
136
|
+
id: id,
|
137
|
+
name: name,
|
138
|
+
type: type,
|
139
|
+
gender: gender,
|
140
|
+
size: size,
|
141
|
+
more_names: more_names )
|
96
142
|
end
|
97
143
|
recs
|
98
144
|
end # method build_recs
|
@@ -116,27 +162,80 @@ module Cryptopunks
|
|
116
162
|
alias_method :sheet, :spritesheet
|
117
163
|
|
118
164
|
|
165
|
+
def records() @recs; end
|
166
|
+
alias_method :meta, :records
|
119
167
|
|
120
168
|
|
121
169
|
|
122
|
-
|
170
|
+
|
171
|
+
def find_meta( q, gender: nil,
|
172
|
+
size: nil,
|
173
|
+
style: nil ) ## note: gender (m/f) required for attributes!!!
|
123
174
|
|
124
175
|
key = normalize_key( q ) ## normalize q(uery) string/symbol
|
125
|
-
key << "_(#{normalize_gender( gender )})" if gender
|
126
176
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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'
|
131
214
|
else
|
132
|
-
|
215
|
+
keys << key
|
133
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
|
227
|
+
end
|
228
|
+
|
229
|
+
if rec.nil?
|
230
|
+
puts "!! WARN - no lookup found for #{keys.size} key(s) >#{keys.inspect}<"
|
231
|
+
end
|
232
|
+
|
134
233
|
rec
|
135
234
|
end
|
136
235
|
|
137
236
|
|
138
|
-
def find( q, gender: nil ) ## gender (m/f) required for attributes!!!
|
139
|
-
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 )
|
140
239
|
|
141
240
|
## return image if record found
|
142
241
|
rec ? @sheet[ rec.id ] : nil
|
@@ -145,10 +244,11 @@ module Cryptopunks
|
|
145
244
|
|
146
245
|
|
147
246
|
|
148
|
-
def to_recs( *values )
|
247
|
+
def to_recs( *values, style: nil )
|
149
248
|
archetype_name = values[0]
|
150
249
|
|
151
250
|
### todo/fix: check for nil/not found!!!!
|
251
|
+
## todo/check/fix: assert meta record returned is archetype NOT attribute!!!!
|
152
252
|
archetype = find_meta( archetype_name )
|
153
253
|
if archetype.nil?
|
154
254
|
puts "!! ERROR - archetype >#{archetype}< not found; sorry"
|
@@ -160,11 +260,15 @@ module Cryptopunks
|
|
160
260
|
attribute_names = values[1..-1]
|
161
261
|
## note: attribute lookup requires gender from archetype!!!!
|
162
262
|
attribute_gender = archetype.gender
|
263
|
+
attribute_size = archetype.size
|
163
264
|
|
164
265
|
attribute_names.each do |attribute_name|
|
165
|
-
attribute = find_meta( attribute_name,
|
266
|
+
attribute = find_meta( attribute_name,
|
267
|
+
gender: attribute_gender,
|
268
|
+
size: attribute_size,
|
269
|
+
style: style )
|
166
270
|
if attribute.nil?
|
167
|
-
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"
|
168
272
|
exit 1
|
169
273
|
end
|
170
274
|
recs << attribute
|
@@ -176,12 +280,13 @@ module Cryptopunks
|
|
176
280
|
|
177
281
|
|
178
282
|
|
179
|
-
def generate_image( *values,
|
283
|
+
def generate_image( *values, style: nil,
|
284
|
+
background: nil )
|
180
285
|
|
181
286
|
ids = if values[0].is_a?( Integer ) ## assume integer number (indexes)
|
182
287
|
values
|
183
288
|
else ## assume strings (names)
|
184
|
-
to_recs( *values ).map { |rec| rec.id }
|
289
|
+
to_recs( *values, style: style ).map { |rec| rec.id }
|
185
290
|
end
|
186
291
|
|
187
292
|
|
data/lib/cryptopunks/image.rb
CHANGED
@@ -1,45 +1,6 @@
|
|
1
1
|
module Cryptopunks
|
2
2
|
|
3
3
|
|
4
|
-
class Design ## todo/fix - move to its own file!!!
|
5
|
-
|
6
|
-
end # class Design
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
##############
|
11
|
-
## todo/check:
|
12
|
-
## find a better way to (auto?) include more designs?
|
13
|
-
class DesignSeries ## find a better name for class (just use Series?) - why? why not?
|
14
|
-
def self.build( dir )
|
15
|
-
data = {}
|
16
|
-
paths = Dir.glob( "#{dir}/**.txt" )
|
17
|
-
paths.each do |path|
|
18
|
-
basename = File.basename( path, File.extname( path ) )
|
19
|
-
text = File.open( path, 'r:utf-8' ) { |f| f.read }
|
20
|
-
## todo/check: auto-parse "ahead of time" here
|
21
|
-
## or keep "raw" text - why? why not?
|
22
|
-
data[ basename ] = text
|
23
|
-
end
|
24
|
-
data
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize( dir )
|
28
|
-
@dir = dir # e.g. "#{Cryptopunks.root}/config/more"
|
29
|
-
end
|
30
|
-
|
31
|
-
def data
|
32
|
-
## note: lazy load / build on first demand only
|
33
|
-
@data ||= self.class.build( @dir )
|
34
|
-
end
|
35
|
-
|
36
|
-
def [](key) data[ key ]; end
|
37
|
-
def size() data.size; end
|
38
|
-
def keys() data.keys; end
|
39
|
-
def to_h() data; end ## todo/check: use to_hash() - why? why not?
|
40
|
-
end # class DesignSeries
|
41
|
-
|
42
|
-
|
43
4
|
|
44
5
|
class Image
|
45
6
|
|
@@ -49,65 +10,16 @@ def self.read( path ) ## convenience helper
|
|
49
10
|
end
|
50
11
|
|
51
12
|
|
13
|
+
|
14
|
+
### keep design & colors keyword args in c'tor here
|
15
|
+
## or use parse() like in pixelart - why? why not?
|
16
|
+
|
52
17
|
def initialize( initial=nil, design: nil,
|
53
18
|
colors: nil )
|
54
19
|
if initial
|
55
20
|
## pass image through as-is
|
56
21
|
img = initial
|
57
22
|
else
|
58
|
-
|
59
|
-
## todo/fix:
|
60
|
-
## move design code into design class!!!
|
61
|
-
## for now assume design is a string
|
62
|
-
## split into parts
|
63
|
-
## original/alien-male or original@alien-male
|
64
|
-
## more/alien-female or more@alien-female
|
65
|
-
## original/human-male+darker or original@human-male!darker ????
|
66
|
-
## human-male!darker ?????
|
67
|
-
## keep @ as separator too - why? why not?
|
68
|
-
parts = design.split( %r{[@/]} )
|
69
|
-
parts.unshift( '*' ) if parts.size == 1 ## assume "all-in-one" series (use * as name/id/placeholder)
|
70
|
-
|
71
|
-
series_key = parts[0]
|
72
|
-
design_composite = parts[1]
|
73
|
-
|
74
|
-
## todo/check - find a way for unambigious (color) variant key
|
75
|
-
## use unique char e.g. +*!# or such
|
76
|
-
more_parts = design_composite.split( %r{[!+]} )
|
77
|
-
design_key = more_parts[0]
|
78
|
-
variant_key = more_parts[1] ## color variant for now (for humans) e.g. lighter/light/dark/darker
|
79
|
-
|
80
|
-
series = if ['*','**','_','__'].include?( series_key )
|
81
|
-
DESIGNS ## use all-series-in-one collection
|
82
|
-
else
|
83
|
-
case series_key
|
84
|
-
when 'original' then DESIGNS_ORIGINAL
|
85
|
-
when 'more' then DESIGNS_MORE
|
86
|
-
else raise ArgumentError, "unknown design series >#{series_key}<; sorry"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
design = series[ design_key ]
|
91
|
-
raise ArgumentError, "unknow design >#{design_key}< in series >#{series_key}<; sorry" if design.nil?
|
92
|
-
|
93
|
-
if colors.nil? ## try to auto-fill in colors
|
94
|
-
## note: (auto-)remove _male,_female qualifier if exist
|
95
|
-
colors_key = design_key.sub( '-male', '' ).sub( '-female', '' )
|
96
|
-
colors = COLORS[ colors_key ]
|
97
|
-
|
98
|
-
## allow / support color scheme variants (e.g. lighter/light/dark/darker) etc.
|
99
|
-
if colors.is_a?(Hash)
|
100
|
-
if variant_key
|
101
|
-
colors = colors[ variant_key ]
|
102
|
-
raise ArgumentError, "no colors defined for variant >#{variant_key}< for design >#{design_key}< in series >#{series_key}<; sorry" if colors.nil?
|
103
|
-
else ## note: use (fallback to) first color scheme if no variant key present
|
104
|
-
colors = colors[ colors.keys[0] ]
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
raise ArgumentError, "no (default) colors defined for design >#{design_key}< in series >#{series_key}<; sorry" if colors.nil?
|
109
|
-
end
|
110
|
-
|
111
23
|
## note: unwrap inner image before passing on to super c'tor
|
112
24
|
img = Pixelart::Image.parse( design, colors: colors ).image
|
113
25
|
end
|