cryptopunks 1.1.0 → 1.2.2
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.
- checksums.yaml +4 -4
- data/Manifest.txt +25 -0
- data/README.md +125 -39
- data/Rakefile +2 -3
- data/config/more/alien-female.txt +34 -0
- data/config/more/ape-female.txt +33 -0
- data/config/more/demon-female.txt +33 -0
- data/config/more/demon-male.txt +33 -0
- data/config/more/mummy-female.txt +33 -0
- data/config/more/mummy-male.txt +33 -0
- data/config/more/orc-female.txt +33 -0
- data/config/more/orc-male.txt +33 -0
- data/config/more/robot-female.txt +34 -0
- data/config/more/robot-male.txt +33 -0
- data/config/more/skeleton-female.txt +33 -0
- data/config/more/skeleton-male.txt +33 -0
- data/config/more/vampire-female.txt +33 -0
- data/config/more/vampire-male.txt +33 -0
- data/config/more/zombie-female.txt +33 -0
- data/config/original/alien-male.txt +33 -0
- data/config/original/ape-male.txt +33 -0
- data/config/original/human-female.txt +33 -0
- data/config/original/human-male.txt +34 -0
- data/config/original/zombie-male.txt +33 -0
- data/config/spritesheet.csv +237 -0
- data/config/spritesheet.png +0 -0
- data/lib/cryptopunks/colors.rb +162 -0
- data/lib/cryptopunks/composite.rb +39 -0
- data/lib/cryptopunks/generator.rb +204 -0
- data/lib/cryptopunks/image.rb +107 -70
- data/lib/cryptopunks/structs.rb +19 -6
- data/lib/cryptopunks/version.rb +2 -2
- data/lib/cryptopunks.rb +71 -10
- metadata +49 -18
@@ -0,0 +1,162 @@
|
|
1
|
+
|
2
|
+
ROBOT_COLORS = [
|
3
|
+
'000000', # color 1 - BLACK
|
4
|
+
'535353', # color 2 - BASE 2 (DARKER)
|
5
|
+
'A4A4A4', # color 3 - BASE 1
|
6
|
+
'A9F7FF', # color 4 - BASE 3 (LIGHTER) - eyes
|
7
|
+
]
|
8
|
+
|
9
|
+
VAMPIRE_COLORS = [
|
10
|
+
'000000', # color 1 - BLACK
|
11
|
+
'131313', # color 2 - BASE 4 (DARKEST)
|
12
|
+
'535353', # color 3 - BASE 3 (DARKERER) - eyes
|
13
|
+
'A4A4A4', # color 4 - BASE 2 (DARKER) - eyes
|
14
|
+
'E0E0E0', # color 5 - BASE 1
|
15
|
+
'F6000B', # color 6 - BASE 5 - teeth - red
|
16
|
+
]
|
17
|
+
|
18
|
+
MUMMY_COLORS = [
|
19
|
+
'000000', # color 1 - BLACK
|
20
|
+
'1F1A15', # color 2 - BASE 5 (DARKEST)
|
21
|
+
'2A231C', # color 3 - BASE 4 (DARKERERER)
|
22
|
+
'5F5147', # color 4 - BASE 3 (DARKERER)
|
23
|
+
'927B6A', # color 5 - BASE 2 (DARKER)
|
24
|
+
'D9B599', # color 6 - BASE 1
|
25
|
+
'F6000B', # color 7 - BASE 6 - eyes - red
|
26
|
+
]
|
27
|
+
|
28
|
+
ORC_COLORS = [
|
29
|
+
'000000', # color 1 - BLACK
|
30
|
+
'171a08', # color 2 - BASE 3 (DARKEST)
|
31
|
+
'333F0C', # color 3 - BASE 2 (DARKER)
|
32
|
+
'50650E', # color 4 - BASE 1
|
33
|
+
'FFFFFF', # color 5 - BASE 4 - white
|
34
|
+
]
|
35
|
+
|
36
|
+
SKELETON_COLORS = [
|
37
|
+
'000000', # color 1 - BLACK
|
38
|
+
'e0e0e0', # color 2 - BASE 1
|
39
|
+
]
|
40
|
+
|
41
|
+
DEMON_COLORS = [
|
42
|
+
'000000', # color 1 - BLACK
|
43
|
+
'390102', # color 2 - BASE 3 (DARKEST) - eyes
|
44
|
+
'630006', # color 3 - BASE 2 (DARKER) - eyes
|
45
|
+
'850008', # color 4 - BASE 1
|
46
|
+
]
|
47
|
+
|
48
|
+
ZOMBIE_COLORS = [
|
49
|
+
'000000', # color 1 - BLACK
|
50
|
+
'5e7253', # color 2 - BASE 2 (DARKER)
|
51
|
+
'7da269', # color 3 - BASE 1
|
52
|
+
'ff0000', # color 4 - BASE 3 - red eye
|
53
|
+
'9bbc88', # color 5 - BASE 4 (LIGHTER)
|
54
|
+
'4a010d', # color 6 - BASE 5 - mouth (female only)
|
55
|
+
]
|
56
|
+
|
57
|
+
APE_COLORS = [
|
58
|
+
'000000', # color 1 - BLACK
|
59
|
+
'352410', # color 2 - BASE 3 (DARKEST)
|
60
|
+
'6a563f', # color 3 - BASE 2 (DARKER) - eyes
|
61
|
+
'856f56', # color 4 - BASE 1
|
62
|
+
'a98c6b', # color 5 - BASE 4 (LIGHTER) - eyes
|
63
|
+
]
|
64
|
+
|
65
|
+
ALIEN_COLORS = [
|
66
|
+
'000000', # color 1 - BLACK
|
67
|
+
'75bdbd', # color 2 - BASE 3 (DARKEST)
|
68
|
+
'9be0e0', # color 3 - BASE 2 (DARKER)
|
69
|
+
'c8fbfb', # color 4 - BASE 1
|
70
|
+
]
|
71
|
+
|
72
|
+
|
73
|
+
HUMAN_LIGHTER_BASE1 = 'ead9d9' # rgb(234 217 217) - hsl( 0° 29% 88%)
|
74
|
+
HUMAN_LIGHTER_BASE2 = 'c9b2b2' # rgb(201 178 178) - hsl( 0° 18% 74%) - eyes
|
75
|
+
HUMAN_LIGHTER_BASE3 = 'a58d8d' # rgb(165 141 141) - hsl( 0° 12% 60%) - eyes
|
76
|
+
HUMAN_LIGHTER_BASE4 = 'ffffff' # rgb(255 255 255) - hsl( 0° 0% 100%) -- white
|
77
|
+
HUMAN_LIGHTER_BASE5 = '711010' # rgb(113 16 16) - hsl( 0° 75% 25%) - mouth
|
78
|
+
|
79
|
+
HUMAN_LIGHT_BASE1 = 'dbb180' # rgb(219 177 128) - hsl( 32° 56% 68%)
|
80
|
+
HUMAN_LIGHT_BASE2 = 'd29d60' # rgb(210 157 96) - hsl( 32° 56% 60%) - eyes
|
81
|
+
HUMAN_LIGHT_BASE3 = 'a66e2c' # rgb(166 110 44) - hsl( 32° 58% 41%) - eyes
|
82
|
+
HUMAN_LIGHT_BASE4 = 'e7cba9' # rgb(231 203 169) - hsl( 33° 56% 78%)
|
83
|
+
HUMAN_LIGHT_BASE5 = '711010' # rgb(113 16 16) - hsl( 0° 75% 25%) - mouth
|
84
|
+
|
85
|
+
HUMAN_DARK_BASE1 = 'ae8b61' # rgb(174 139 97) - hsl( 33° 32% 53%)
|
86
|
+
HUMAN_DARK_BASE2 = 'a77c47' # rgb(167 124 71) - hsl( 33° 40% 47%) - eyes
|
87
|
+
HUMAN_DARK_BASE3 = '86581e' # rgb(134 88 30) - hsl( 33° 63% 32%) - eyes
|
88
|
+
HUMAN_DARK_BASE4 = 'b69f82' # rgb(182 159 130) - hsl( 33° 26% 61%)
|
89
|
+
HUMAN_DARK_BASE5 = '5f1d09' # rgb( 95 29 9) - hsl( 14° 83% 20%) - mouth
|
90
|
+
|
91
|
+
HUMAN_DARKER_BASE1 = '713f1d' # rgb(113 63 29) - hsl( 24° 59% 28%)
|
92
|
+
HUMAN_DARKER_BASE2 = '723709' # rgb(114 55 9) - hsl( 26° 85% 24%) - eyes
|
93
|
+
HUMAN_DARKER_BASE3 = '562600' # rgb( 86 38 0) - hsl( 27° 100% 17%) - eyes
|
94
|
+
HUMAN_DARKER_BASE4 = '8b532c' # rgb(139 83 44) - hsl( 25° 52% 36%)
|
95
|
+
HUMAN_DARKER_BASE5 = '4a1201' # rgb( 74 18 1) - hsl( 14° 97% 15%) - mouth
|
96
|
+
|
97
|
+
|
98
|
+
HUMAN_COLORS_LIGHT = [ ## todo/check: change to HUMAN_LIGHT_COLORS???
|
99
|
+
'000000', # color 1 - BLACK
|
100
|
+
HUMAN_LIGHT_BASE3, # color 2 - BASE 3 (DARKEST) - eyes
|
101
|
+
HUMAN_LIGHT_BASE2, # color 3 - BASE 2 (DARKER) - eyes
|
102
|
+
HUMAN_LIGHT_BASE1, # color 4 - BASE 1
|
103
|
+
HUMAN_LIGHT_BASE4, # color 5 - BASE 4
|
104
|
+
HUMAN_LIGHT_BASE5, # color 6 - BASE 5 - mouth (femaly only)
|
105
|
+
]
|
106
|
+
|
107
|
+
HUMAN_COLORS_LIGHTER = [ ## todo/check: change to HUMAN_LIGHTER_COLORS?? or add alias - why? why not?
|
108
|
+
'000000', # color 1 - BLACK
|
109
|
+
HUMAN_LIGHTER_BASE3,
|
110
|
+
HUMAN_LIGHTER_BASE2,
|
111
|
+
HUMAN_LIGHTER_BASE1,
|
112
|
+
HUMAN_LIGHTER_BASE4,
|
113
|
+
HUMAN_LIGHTER_BASE5,
|
114
|
+
]
|
115
|
+
|
116
|
+
HUMAN_COLORS_DARK = [
|
117
|
+
'000000', # color 1 - BLACK
|
118
|
+
HUMAN_DARK_BASE3,
|
119
|
+
HUMAN_DARK_BASE2,
|
120
|
+
HUMAN_DARK_BASE1,
|
121
|
+
HUMAN_DARK_BASE4,
|
122
|
+
HUMAN_DARK_BASE5,
|
123
|
+
]
|
124
|
+
|
125
|
+
HUMAN_COLORS_DARKER = [
|
126
|
+
'000000', # color 1 - BLACK
|
127
|
+
HUMAN_DARKER_BASE3,
|
128
|
+
HUMAN_DARKER_BASE2,
|
129
|
+
HUMAN_DARKER_BASE1,
|
130
|
+
HUMAN_DARKER_BASE4,
|
131
|
+
HUMAN_DARKER_BASE5,
|
132
|
+
]
|
133
|
+
|
134
|
+
|
135
|
+
HUMAN_COLORS = {
|
136
|
+
'light' => HUMAN_COLORS_LIGHT,
|
137
|
+
'lighter' => HUMAN_COLORS_LIGHTER,
|
138
|
+
'dark' => HUMAN_COLORS_DARK,
|
139
|
+
'darker' => HUMAN_COLORS_DARKER,
|
140
|
+
}
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
### todo/check:
|
145
|
+
## use a different name other than the "generic" COLORS - why? why not?
|
146
|
+
|
147
|
+
COLORS = {
|
148
|
+
## original series
|
149
|
+
'human' => HUMAN_COLORS,
|
150
|
+
'zombie' => ZOMBIE_COLORS,
|
151
|
+
'ape' => APE_COLORS,
|
152
|
+
'alien' => ALIEN_COLORS,
|
153
|
+
## more series
|
154
|
+
'vampire' => VAMPIRE_COLORS,
|
155
|
+
'mummy' => MUMMY_COLORS,
|
156
|
+
'orc' => ORC_COLORS,
|
157
|
+
'skeleton' => SKELETON_COLORS,
|
158
|
+
'demon' => DEMON_COLORS,
|
159
|
+
'robot' => ROBOT_COLORS,
|
160
|
+
}
|
161
|
+
|
162
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Cryptopunks
|
2
|
+
class Image ## nest Composite inside Image - why? why not?
|
3
|
+
class Composite < Pixelart::ImageComposite
|
4
|
+
|
5
|
+
PUNK_HASH = 'ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b'
|
6
|
+
|
7
|
+
def self.sha256( data )
|
8
|
+
## todo/check: or just use Digest::SHA256.hexdigest - why? why not?
|
9
|
+
Digest::SHA256.digest( data ).unpack( 'H*' )[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
PUNK_HEIGHT = 24
|
14
|
+
PUNK_WIDTH = 24
|
15
|
+
|
16
|
+
def self.read( path='./punks.png', width: PUNK_WIDTH, height: PUNK_HEIGHT )
|
17
|
+
data = File.open( path, 'rb' ) { |f| f.read }
|
18
|
+
|
19
|
+
hexdigest = sha256( data ) ## check sha256 checksum
|
20
|
+
if hexdigest == PUNK_HASH
|
21
|
+
puts " >#{hexdigest}< SHA256 hash matching"
|
22
|
+
puts " ✓ True Official Genuine CryptoPunks™ verified"
|
23
|
+
else
|
24
|
+
puts " ✓ True Official Genuine Yes, You Can! Punks Not Dead™ verified"
|
25
|
+
end
|
26
|
+
|
27
|
+
img = ChunkyPNG::Image.from_blob( data )
|
28
|
+
new( img, width: width, height: height )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def initialize( *args, width: PUNK_WIDTH,
|
33
|
+
height: PUNK_HEIGHT )
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
end ## class Composite
|
38
|
+
end ## class Image
|
39
|
+
end ## module Cryptopunks
|
@@ -0,0 +1,204 @@
|
|
1
|
+
|
2
|
+
module Cryptopunks
|
3
|
+
|
4
|
+
class Metadata
|
5
|
+
### todo/fix:
|
6
|
+
## move into Punks::Metadata or such
|
7
|
+
class Sprite
|
8
|
+
attr_reader :id, :name, :type, :gender
|
9
|
+
|
10
|
+
|
11
|
+
def initialize( id:,
|
12
|
+
name:,
|
13
|
+
type:,
|
14
|
+
gender: )
|
15
|
+
@id = id # zero-based index eg. 0,1,2,3, etc.
|
16
|
+
@name = name
|
17
|
+
@type = type
|
18
|
+
@gender = gender
|
19
|
+
end
|
20
|
+
|
21
|
+
## todo/check - find better names for type attribute/archetypes?
|
22
|
+
## use (alternate name/alias) base or face for archetypes? any others?
|
23
|
+
def attribute?() @type.downcase.start_with?( 'attribute' ); end
|
24
|
+
def archetype?() @type.downcase.start_with?( 'archetype' ); end
|
25
|
+
end # class Metadata::Sprite
|
26
|
+
end # class Metadata
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
class Generator
|
32
|
+
|
33
|
+
######
|
34
|
+
# static helpers - (turn into "true" static self.class methods - why? why not?)
|
35
|
+
#
|
36
|
+
def normalize_key( str )
|
37
|
+
str.downcase.gsub(/[ ()°_-]/, '').strip
|
38
|
+
end
|
39
|
+
|
40
|
+
def normalize_gender( str )
|
41
|
+
## e.g. Female => f
|
42
|
+
## F => f
|
43
|
+
## always return f or m
|
44
|
+
str.downcase[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def build_attributes_by_name( recs )
|
49
|
+
h = {}
|
50
|
+
recs.each_with_index do |rec|
|
51
|
+
key = normalize_key( rec.name )
|
52
|
+
key << "_(#{rec.gender})" if rec.attribute?
|
53
|
+
|
54
|
+
if h[ key ]
|
55
|
+
puts "!!! ERROR - attribute name is not unique:"
|
56
|
+
pp rec
|
57
|
+
puts "duplicate:"
|
58
|
+
pp h[key]
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
h[ key ] = rec
|
62
|
+
end
|
63
|
+
## pp h
|
64
|
+
h
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def build_recs( recs ) ## build and normalize (meta data) records
|
69
|
+
|
70
|
+
## sort by id
|
71
|
+
recs = recs.sort do |l,r|
|
72
|
+
l['id'].to_i( 10 ) <=> r['id'].to_i( 10 ) # use base10 (decimal)
|
73
|
+
end
|
74
|
+
|
75
|
+
## assert all recs are in order by id (0 to size)
|
76
|
+
recs.each_with_index do |rec, exp_id|
|
77
|
+
id = rec['id'].to_i(10)
|
78
|
+
if id != exp_id
|
79
|
+
puts "!! ERROR - meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
## convert to "wrapped / immutable" kind-of struct
|
85
|
+
recs = recs.map do |rec|
|
86
|
+
id = rec['id'].to_i( 10 )
|
87
|
+
name = rec['name']
|
88
|
+
gender = normalize_gender( rec['gender'] )
|
89
|
+
type = rec['type']
|
90
|
+
|
91
|
+
Metadata::Sprite.new(
|
92
|
+
id: id,
|
93
|
+
name: name,
|
94
|
+
type: type,
|
95
|
+
gender: gender)
|
96
|
+
end
|
97
|
+
recs
|
98
|
+
end # method build_recs
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def initialize( image_path="./spritesheet.png",
|
104
|
+
meta_path="./spritesheet.csv" )
|
105
|
+
@sheet = Pixelart::ImageComposite.read( image_path )
|
106
|
+
recs = CsvHash.read( meta_path )
|
107
|
+
|
108
|
+
@recs = build_recs( recs )
|
109
|
+
|
110
|
+
## lookup by "case/space-insensitive" name / key
|
111
|
+
@attributes_by_name = build_attributes_by_name( @recs )
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def spritesheet() @sheet; end
|
116
|
+
alias_method :sheet, :spritesheet
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
def find_meta( q, gender: nil ) ## gender (m/f) required for attributes!!!
|
123
|
+
|
124
|
+
key = normalize_key( q ) ## normalize q(uery) string/symbol
|
125
|
+
key << "_(#{normalize_gender( gender )})" if gender
|
126
|
+
|
127
|
+
rec = @attributes_by_name[ key ]
|
128
|
+
if rec
|
129
|
+
puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type} (#{rec.gender})"
|
130
|
+
# pp rec
|
131
|
+
else
|
132
|
+
puts "!! WARN - no lookup found for key >#{key}<"
|
133
|
+
end
|
134
|
+
rec
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def find( q, gender: nil ) ## gender (m/f) required for attributes!!!
|
139
|
+
rec = find_meta( q, gender: gender )
|
140
|
+
|
141
|
+
## return image if record found
|
142
|
+
rec ? @sheet[ rec.id ] : nil
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
def to_recs( *values )
|
149
|
+
archetype_name = values[0]
|
150
|
+
|
151
|
+
### todo/fix: check for nil/not found!!!!
|
152
|
+
archetype = find_meta( archetype_name )
|
153
|
+
if archetype.nil?
|
154
|
+
puts "!! ERROR - archetype >#{archetype}< not found; sorry"
|
155
|
+
exit 1
|
156
|
+
end
|
157
|
+
|
158
|
+
recs = [archetype]
|
159
|
+
|
160
|
+
attribute_names = values[1..-1]
|
161
|
+
## note: attribute lookup requires gender from archetype!!!!
|
162
|
+
attribute_gender = archetype.gender
|
163
|
+
|
164
|
+
attribute_names.each do |attribute_name|
|
165
|
+
attribute = find_meta( attribute_name, gender: attribute_gender )
|
166
|
+
if attribute.nil?
|
167
|
+
puts "!! ERROR - attribute >#{attribute_name}< for (#{attribute_gender}) not found; sorry"
|
168
|
+
exit 1
|
169
|
+
end
|
170
|
+
recs << attribute
|
171
|
+
end
|
172
|
+
|
173
|
+
recs
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
def generate_image( *values, background: nil )
|
180
|
+
|
181
|
+
ids = if values[0].is_a?( Integer ) ## assume integer number (indexes)
|
182
|
+
values
|
183
|
+
else ## assume strings (names)
|
184
|
+
to_recs( *values ).map { |rec| rec.id }
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
punk = Pixelart::Image.new( 24, 24 )
|
189
|
+
|
190
|
+
if background ## for now assume background is (simply) color
|
191
|
+
punk.compose!( Pixelart::Image.new( 24, 24, background ) )
|
192
|
+
end
|
193
|
+
|
194
|
+
ids.each do |id|
|
195
|
+
punk.compose!( @sheet[ id ] )
|
196
|
+
end
|
197
|
+
|
198
|
+
punk
|
199
|
+
end
|
200
|
+
alias_method :generate, :generate_image
|
201
|
+
|
202
|
+
end # class Generator
|
203
|
+
|
204
|
+
end # module Cryptopunks
|
data/lib/cryptopunks/image.rb
CHANGED
@@ -1,84 +1,121 @@
|
|
1
1
|
module Cryptopunks
|
2
|
-
class Image
|
3
|
-
def self.read( path='./punks.png' )
|
4
|
-
data = File.open( path, 'rb' ) { |f| f.read }
|
5
|
-
new( data )
|
6
|
-
end
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor :zoom
|
10
2
|
|
11
|
-
PUNK_ROWS = 100
|
12
|
-
PUNK_COLS = 100
|
13
|
-
PUNK_COUNT = PUNK_ROWS * PUNK_COLS ## 10_000 = 100x100 (24000x24000 pixel)
|
14
3
|
|
15
|
-
|
16
|
-
PUNK_WIDTH = 24
|
4
|
+
class Design ## todo/fix - move to its own file!!!
|
17
5
|
|
18
|
-
|
6
|
+
end # class Design
|
19
7
|
|
20
8
|
|
21
|
-
def initialize( data )
|
22
|
-
@punks = ChunkyPNG::Image.from_blob( data )
|
23
|
-
puts " #{@punks.height}x#{@punks.width} (height x width)"
|
24
|
-
|
25
|
-
## check sha256 checksum
|
26
|
-
@hexdigest = sha256( data )
|
27
|
-
if original?
|
28
|
-
puts " >#{@hexdigest}< SHA256 hash matching"
|
29
|
-
puts " ✓ True Official Genuine CryptoPunks™ verified"
|
30
|
-
else
|
31
|
-
puts " !! ERROR: >#{hexdigest}< SHA256 hash NOT matching"
|
32
|
-
puts " >#{PUNK_HASH}< expected for True Official Genuine CryptoPunks™."
|
33
|
-
puts ""
|
34
|
-
puts " Sorry, please download the original."
|
35
|
-
exit 1
|
36
|
-
end
|
37
9
|
|
38
|
-
|
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
|
39
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
|
+
|
44
|
+
class Image
|
45
|
+
|
46
|
+
def self.read( path ) ## convenience helper
|
47
|
+
img = ChunkyPNG::Image.from_file( path )
|
48
|
+
new( img )
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def initialize( initial=nil, design: nil,
|
53
|
+
colors: nil )
|
54
|
+
if initial
|
55
|
+
## pass image through as-is
|
56
|
+
img = initial
|
57
|
+
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
|
40
110
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def verify?() @hexdigest == PUNK_HASH; end
|
45
|
-
alias_method :genuine?, :verify?
|
46
|
-
alias_method :original?, :verify?
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def size() PUNK_COUNT; end
|
51
|
-
|
52
|
-
def []( index )
|
53
|
-
@zoom == 1 ? crop( index ) : scale( index, @zoom )
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
def crop( index )
|
58
|
-
y, x = index.divmod( PUNK_ROWS )
|
59
|
-
@punks.crop( x*PUNK_WIDTH, y*PUNK_HEIGHT, PUNK_WIDTH, PUNK_HEIGHT )
|
111
|
+
## note: unwrap inner image before passing on to super c'tor
|
112
|
+
img = Pixelart::Image.parse( design, colors: colors ).image
|
60
113
|
end
|
61
114
|
|
115
|
+
super( img.width, img.height, img )
|
116
|
+
end
|
62
117
|
|
63
|
-
def scale( index, zoom )
|
64
|
-
punk = ChunkyPNG::Image.new( PUNK_WIDTH*zoom, PUNK_HEIGHT*zoom,
|
65
|
-
ChunkyPNG::Color::WHITE )
|
66
118
|
|
67
|
-
## (x,y) offset in big all-in-one punks image
|
68
|
-
y, x = index.divmod( PUNK_ROWS )
|
69
119
|
|
70
|
-
|
71
|
-
|
72
|
-
PUNK_HEIGHT.times do |j|
|
73
|
-
pixel = @punks[i+x*PUNK_WIDTH, j+y*PUNK_HEIGHT]
|
74
|
-
zoom.times do |n|
|
75
|
-
zoom.times do |m|
|
76
|
-
punk[n+zoom*i,m+zoom*j] = pixel
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
punk
|
82
|
-
end
|
83
|
-
end ## class Image
|
84
|
-
end ## module Cryptopunks
|
120
|
+
end # class Image
|
121
|
+
end # module Cryptopunks
|
data/lib/cryptopunks/structs.rb
CHANGED
@@ -137,12 +137,25 @@ end ## (nested) class Accessory
|
|
137
137
|
@birthday = Date.new( 2017, 6, 23) ## all 10,000 minted on June 23, 2017
|
138
138
|
end
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
def
|
145
|
-
def
|
140
|
+
def is_type?( name ) @type.name == name; end
|
141
|
+
alias_method :is?, :is_type?
|
142
|
+
|
143
|
+
## convenience helpers for "classic" (5) types
|
144
|
+
def alien?() is_type?( 'Alien'); end
|
145
|
+
def ape?() is_type?( 'Ape' ); end
|
146
|
+
def zombie?() is_type?( 'Zombie' ); end
|
147
|
+
def female?() is_type?( 'Female' ); end
|
148
|
+
def male?() is_type?( 'Male' ); end
|
149
|
+
|
150
|
+
## convenience helpers to lookup attributes
|
151
|
+
def has_attribute?( name )
|
152
|
+
accessories.each do |acc|
|
153
|
+
return true if acc.name == name
|
154
|
+
end
|
155
|
+
false
|
156
|
+
end
|
157
|
+
alias_method :has?, :has_attribute?
|
158
|
+
alias_method :include?, :has_attribute?
|
146
159
|
end # class Metadata
|
147
160
|
|
148
161
|
end # module Cryptopunks
|