cryptopunks 1.0.0 → 1.2.1
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 +26 -0
- data/README.md +129 -77
- data/Rakefile +3 -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/lib/cryptopunks.rb +49 -90
- data/lib/cryptopunks/attributes.rb +147 -0
- data/lib/cryptopunks/colors.rb +162 -0
- data/lib/cryptopunks/composite.rb +38 -0
- data/lib/cryptopunks/dataset.rb +67 -0
- data/lib/cryptopunks/image.rb +121 -0
- data/lib/cryptopunks/structs.rb +161 -0
- data/lib/cryptopunks/version.rb +2 -2
- metadata +51 -5
@@ -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,38 @@
|
|
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
|
+
def self.read( path='./punks.png' )
|
13
|
+
data = File.open( path, 'rb' ) { |f| f.read }
|
14
|
+
|
15
|
+
hexdigest = sha256( data ) ## check sha256 checksum
|
16
|
+
if hexdigest == PUNK_HASH
|
17
|
+
puts " >#{hexdigest}< SHA256 hash matching"
|
18
|
+
puts " ✓ True Official Genuine CryptoPunks™ verified"
|
19
|
+
else
|
20
|
+
puts " ✓ True Official Genuine Yes, You Can! Punks Not Dead™ verified"
|
21
|
+
end
|
22
|
+
|
23
|
+
img = ChunkyPNG::Image.from_blob( data )
|
24
|
+
new( img )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
PUNK_HEIGHT = 24
|
29
|
+
PUNK_WIDTH = 24
|
30
|
+
|
31
|
+
def initialize( *args, width: PUNK_WIDTH,
|
32
|
+
height: PUNK_HEIGHT )
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
end ## class Composite
|
37
|
+
end ## class Image
|
38
|
+
end ## module Cryptopunks
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Cryptopunks
|
4
|
+
module Dataset
|
5
|
+
|
6
|
+
def self.read( path='./datasets/punks/*.csv' )
|
7
|
+
|
8
|
+
datasets = Dir.glob( path )
|
9
|
+
#=> ["./datasets/punks/0-999.csv",
|
10
|
+
# "./datasets/punks/1000-1999.csv",
|
11
|
+
# "./datasets/punks/2000-2999.csv",
|
12
|
+
# "./datasets/punks/3000-3999.csv",
|
13
|
+
# "./datasets/punks/4000-4999.csv",
|
14
|
+
# "./datasets/punks/5000-5999.csv",
|
15
|
+
# "./datasets/punks/6000-6999.csv",
|
16
|
+
# "./datasets/punks/7000-7999.csv",
|
17
|
+
# "./datasets/punks/8000-8999.csv",
|
18
|
+
# "./datasets/punks/9000-9999.csv"]
|
19
|
+
|
20
|
+
rows = []
|
21
|
+
datasets.each do |dataset|
|
22
|
+
rows += CsvHash.read( dataset )
|
23
|
+
end
|
24
|
+
|
25
|
+
# puts " #{rows.size} rows(s)"
|
26
|
+
#=> 10000 rows(s)
|
27
|
+
|
28
|
+
### wrap in punk struct for easier access
|
29
|
+
punks = []
|
30
|
+
rows.each do |row|
|
31
|
+
id = row['id'].to_i
|
32
|
+
type_q = row['type']
|
33
|
+
count = row['count'].to_i
|
34
|
+
accessories_q = row['accessories'].split( %r{[ ]*/[ ]*} )
|
35
|
+
|
36
|
+
if count != accessories_q.size
|
37
|
+
puts "!! ERROR - punk data assertion failed - expected accessories count #{count}; got #{accessories_q.size}"
|
38
|
+
pp row
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
type = Metadata::Type.find( type_q )
|
43
|
+
if type.nil?
|
44
|
+
puts "!! ERROR - punk data assertion failed - unknown punk type >#{type_q}<"
|
45
|
+
pp row
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
49
|
+
accessories = []
|
50
|
+
accessories_q.each do |acc_q|
|
51
|
+
acc = Metadata::Accessory.find( acc_q )
|
52
|
+
if acc.nil?
|
53
|
+
puts "!! ERROR - punk data assertion failed - unknown punk accessory type >#{acc_q}<"
|
54
|
+
pp row
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
accessories << acc
|
58
|
+
end
|
59
|
+
|
60
|
+
punks << Metadata.new( id, type, accessories )
|
61
|
+
end
|
62
|
+
punks
|
63
|
+
end
|
64
|
+
end # module Dataset
|
65
|
+
end # module Cryptopunks
|
66
|
+
|
67
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Cryptopunks
|
2
|
+
|
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
|
+
|
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 = inital
|
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
|
110
|
+
|
111
|
+
## note: unwrap inner image before passing on to super c'tor
|
112
|
+
img = Pixelart::Image.parse( design, colors: colors ).image
|
113
|
+
end
|
114
|
+
|
115
|
+
super( img.width, img.height, img )
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
end # class Image
|
121
|
+
end # module Cryptopunks
|
@@ -0,0 +1,161 @@
|
|
1
|
+
|
2
|
+
module Cryptopunks
|
3
|
+
### wrap metadata (e.g. punk types, accessories, etc.
|
4
|
+
## in structs for easy/easier access)
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
class Metadata
|
9
|
+
|
10
|
+
## nested class
|
11
|
+
class Type ## todo/check: use alias family or such?
|
12
|
+
attr_reader :name,
|
13
|
+
:limit
|
14
|
+
def initialize( name, limit )
|
15
|
+
@name = name
|
16
|
+
@limit = limit
|
17
|
+
end
|
18
|
+
## def to_s() @name; end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
%Q{<Type "#{@name}", limit: #{@limit}>}
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
def self.build
|
27
|
+
TYPES.reduce( {} ) do |h, rec|
|
28
|
+
type = Type.new( rec[:name], rec[:limit ] )
|
29
|
+
h[ rec[:name].downcase ] = type
|
30
|
+
h
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.registry
|
35
|
+
## auto-build registry (hash table) lookup (by name)
|
36
|
+
@@types ||= build
|
37
|
+
@@types
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.all() registry.values; end
|
41
|
+
|
42
|
+
def self.find( q ) registry[ q.to_s.downcase ]; end
|
43
|
+
end ## (nested) class Type
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
## nested class
|
48
|
+
class AccessoryType
|
49
|
+
attr_reader :name,
|
50
|
+
:accessories
|
51
|
+
def initialize( name, accessories=[] )
|
52
|
+
@name = name
|
53
|
+
@accessories = accessories
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
def self.build
|
59
|
+
ACCESSORY_TYPES.reduce( {} ) do |h, rec|
|
60
|
+
type = AccessoryType.new( rec[:name] )
|
61
|
+
## add all accessories
|
62
|
+
rec[:accessories].each do |rec_acc|
|
63
|
+
type.accessories << Accessory.new( rec_acc[:name],
|
64
|
+
type,
|
65
|
+
rec_acc[:limit].to_i )
|
66
|
+
end
|
67
|
+
h[ rec[:name].downcase ] = type
|
68
|
+
h
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.registry
|
73
|
+
## auto-build registry (hash table) lookup (by name)
|
74
|
+
@@types ||= build
|
75
|
+
@@types
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.all() registry.values; end
|
79
|
+
|
80
|
+
def self.find( q ) registry[ q.to_s.downcase ]; end
|
81
|
+
end ## (nested) class AccessoryType
|
82
|
+
|
83
|
+
|
84
|
+
## nested class
|
85
|
+
class Accessory
|
86
|
+
|
87
|
+
attr_reader :name,
|
88
|
+
:type,
|
89
|
+
:limit
|
90
|
+
def initialize( name, type, limit )
|
91
|
+
@name = name
|
92
|
+
@type = type
|
93
|
+
@limit = limit
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def inspect
|
98
|
+
%Q{<Accessory "#{@name}", type: "#{@type.name}", limit: #{@limit}>}
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
def self.build
|
104
|
+
AccessoryType.all.reduce( {} ) do |h, type|
|
105
|
+
type.accessories.each do |acc|
|
106
|
+
h[ acc.name.downcase ] = acc
|
107
|
+
end
|
108
|
+
h
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.registry
|
113
|
+
## auto-build registry (hash table) lookup (by name)
|
114
|
+
@@types ||= build
|
115
|
+
@@types
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.all() registry.values; end
|
119
|
+
|
120
|
+
def self.find( q ) registry[ q.to_s.downcase ]; end
|
121
|
+
end ## (nested) class Accessory
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
attr_reader :id,
|
129
|
+
:type,
|
130
|
+
:accessories,
|
131
|
+
:birthday ## todo/check: use minted or such?
|
132
|
+
|
133
|
+
def initialize( id, type, accessories )
|
134
|
+
@id = id
|
135
|
+
@type = type
|
136
|
+
@accessories = accessories
|
137
|
+
@birthday = Date.new( 2017, 6, 23) ## all 10,000 minted on June 23, 2017
|
138
|
+
end
|
139
|
+
|
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?
|
159
|
+
end # class Metadata
|
160
|
+
|
161
|
+
end # module Cryptopunks
|