kittyverse 0.3.0 → 0.4.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.
@@ -16,19 +16,16 @@ end
16
16
 
17
17
 
18
18
 
19
- def kitties_fancy_search_url( key, h ) ## todo: find a better name - why? why not?
20
- ## todo/fix: add h[:name] to hash (no need for key)
19
+ def kitties_fancy_search_url( fancy ) ## todo: find a better name - why? why not?
21
20
 
22
21
  ## note: use (official) chinese name for search param if present
23
- param = h[:name_cn] ? h[:name_cn] : key
22
+ param = fancy.name_cn ? fancy.name_cn : fancy.key
24
23
 
25
- if h[:special]
24
+ if fancy.special_edition?
26
25
  q = "specialedition:#{param}" ## todo: urlescape param - why? why not?
27
- elsif h[:exclusive] ## just use fancy too - why? why not?
26
+ elsif fancy.exclusive? ## just use fancy too - why? why not?
28
27
  q = "exclusive:#{param}"
29
- elsif h[:prestige]
30
- q = "purrstige:#{param}"
31
- else ## assume fancy
28
+ else ## assume "normal/regular" fancy
32
29
  q = "fancy:#{param}"
33
30
  end
34
31
 
@@ -36,7 +33,7 @@ def kitties_fancy_search_url( key, h ) ## todo: find a better name - why? why
36
33
  end
37
34
 
38
35
  ## alias :kitties_special_search_url :kitties_fancy_search_url
39
- def kitties_special_search_url( key, h ) kitties_fancy_search_url( key, h); end
36
+ def kitties_specialedition_search_url( fancy ) kitties_fancy_search_url( fancy ); end
40
37
 
41
38
 
42
39
 
@@ -1,377 +1,265 @@
1
1
  # encoding: utf-8
2
2
 
3
- ###
4
- # trais / kaittributes
5
- # see https://cryptokittydex.com/kaittributes
6
- # https://docs.google.com/spreadsheets/d/1ShiBKSglFAB2vJe4Uow3iF09FBVq6ZAaOzTCIECf4iA
7
- # http://www.rolldice.club/cryptokitties/wkt_tree.php
8
- # https://www.cryptokitties.co/cattributes (official cattributes list)
9
- #
10
- #
11
- # for latest updates on new cattributes see:
12
- # https://updates.cryptokitties.co (official latest updates/timeline)
13
-
14
- ## ordered by gene position (0,1,3.4,5,... etc.)
15
- ## 12 traits (4 genes each)
16
- TRAITS =
17
- {
18
- body: {
19
- genes: '0-3',
20
- name: 'Fur', code: 'FU',
21
- kai: {
22
- '1' => 'savannah',
23
- '2' => 'selkirk',
24
- '3' => 'chantilly',
25
- '4' => 'birman',
26
- '5' => 'koladiviya',
27
- '6' => 'bobtail',
28
- '7' => 'manul',
29
- '8' => 'pixiebob',
30
- '9' => 'siberian',
31
- 'a' => 'cymric',
32
- 'b' => 'chartreux',
33
- 'c' => 'himalayan',
34
- 'd' => 'munchkin',
35
- 'e' => 'sphynx',
36
- 'f' => 'ragamuffin',
37
- 'g' => 'ragdoll',
38
- 'h' => 'norwegianforest',
39
- 'i' => 'mekong',
40
- 'j' => 'highlander',
41
- 'k' => 'balinese',
42
- 'm' => 'lynx',
43
- 'n' => 'mainecoon',
44
- 'o' => 'laperm',
45
- 'p' => 'persian',
46
- 'q' => 'fox',
47
- 'r' => 'kurilian',
48
- 's' => 'toyger',
49
- 't' => 'manx',
50
- 'u' => 'lykoi',
51
- 'v' => 'burmilla',
52
- 'w' => 'liger',
53
- 'x' => ''
54
- }
55
- },
56
- pattern: {
57
- genes: '4-7',
58
- name: 'Pattern', code: 'PA',
59
- kai: {
60
- '1' => 'vigilante',
61
- '2' => 'tiger',
62
- '3' => 'rascal',
63
- '4' => 'ganado',
64
- '5' => 'leopard',
65
- '6' => 'camo',
66
- '7' => 'rorschach',
67
- '8' => 'spangled',
68
- '9' => 'calicool',
69
- 'a' => 'luckystripe',
70
- 'b' => 'amur',
71
- 'c' => 'jaguar',
72
- 'd' => 'spock',
73
- 'e' => 'mittens',
74
- 'f' => 'totesbasic 1', ## use totesbasic_f or totesbasic (14) - why? why not?
75
- 'g' => 'totesbasic 2', ## use totesbasic_g or totesbasic (15)
76
- 'h' => 'splat',
77
- 'i' => 'thunderstruck',
78
- 'j' => 'dippedcone',
79
- 'k' => 'highsociety',
80
- 'm' => 'tigerpunk',
81
- 'n' => 'henna',
82
- 'o' => 'arcreactor',
83
- 'p' => 'totesbasic 3', ## use totesbasic_p or totesbasic (23) - why? why not?
84
- 'q' => 'scorpius',
85
- 'r' => 'razzledazzle',
86
- 's' => 'hotrod',
87
- 't' => 'allyouneed',
88
- 'u' => 'avatar',
89
- 'v' => 'gyre',
90
- 'w' => 'moonrise',
91
- 'x' => ''
92
- }
93
- },
94
- coloreyes: {
95
- genes: '8-11',
96
- name: 'Eye Color', code: 'EC',
97
- kai: {
98
- '1' => 'thundergrey',
99
- '2' => 'gold',
100
- '3' => 'topaz',
101
- '4' => 'mintgreen',
102
- '5' => 'isotope',
103
- '6' => 'sizzurp',
104
- '7' => 'chestnut',
105
- '8' => 'strawberry',
106
- '9' => 'sapphire',
107
- 'a' => 'forgetmenot',
108
- 'b' => 'dahlia',
109
- 'c' => 'coralsunrise',
110
- 'd' => 'olive',
111
- 'e' => 'doridnudibranch',
112
- 'f' => 'parakeet',
113
- 'g' => 'cyan',
114
- 'h' => 'pumpkin',
115
- 'i' => 'limegreen',
116
- 'j' => 'bridesmaid',
117
- 'k' => 'bubblegum',
118
- 'm' => 'twilightsparkle',
119
- 'n' => 'palejade',
120
- 'o' => 'pinefresh',
121
- 'p' => 'eclipse',
122
- 'q' => 'babypuke',
123
- 'r' => 'downbythebay',
124
- 's' => 'autumnmoon',
125
- 't' => 'oasis',
126
- 'u' => 'gemini',
127
- 'v' => 'dioscuri',
128
- 'w' => 'kaleidoscope',
129
- 'x' => ''
130
- }
131
- },
132
- eyes: {
133
- genes: '12-15',
134
- name: 'Eye Shape', code: 'ES', ## eye type
135
- kai: {
136
- '1' => 'swarley',
137
- '2' => 'wonky',
138
- '3' => 'serpent',
139
- '4' => 'googly',
140
- '5' => 'otaku',
141
- '6' => 'simple',
142
- '7' => 'crazy',
143
- '8' => 'thicccbrowz',
144
- '9' => 'caffeine',
145
- 'a' => 'wowza',
146
- 'b' => 'baddate',
147
- 'c' => 'asif',
148
- 'd' => 'chronic',
149
- 'e' => 'slyboots',
150
- 'f' => 'wiley',
151
- 'g' => 'stunned',
152
- 'h' => 'chameleon',
153
- 'i' => 'alien',
154
- 'j' => 'fabulous',
155
- 'k' => 'raisedbrow',
156
- 'm' => 'tendertears',
157
- 'n' => 'hacker',
158
- 'o' => 'sass',
159
- 'p' => 'sweetmeloncakes',
160
- 'q' => 'oceanid',
161
- 'r' => 'wingtips',
162
- 's' => 'firedup',
163
- 't' => 'buzzed',
164
- 'u' => 'bornwithit',
165
- 'v' => 'candyshoppe',
166
- 'w' => 'drama',
167
- 'x' => ''
168
- }
169
- },
170
- colorprimary: {
171
- genes: '16-19',
172
- name: 'Base Color', code: 'BC', ## color1 / colorprimary / body color
173
- kai: {
174
- '1' => 'shadowgrey',
175
- '2' => 'salmon',
176
- '3' => 'meowgarine',
177
- '4' => 'orangesoda',
178
- '5' => 'cottoncandy',
179
- '6' => 'mauveover',
180
- '7' => 'aquamarine',
181
- '8' => 'nachocheez',
182
- '9' => 'harbourfog',
183
- 'a' => 'cinderella',
184
- 'b' => 'greymatter',
185
- 'c' => 'tundra',
186
- 'd' => 'brownies',
187
- 'e' => 'dragonfruit',
188
- 'f' => 'hintomint',
189
- 'g' => 'bananacream',
190
- 'h' => 'cloudwhite',
191
- 'i' => 'cornflower',
192
- 'j' => 'oldlace',
193
- 'k' => 'koala',
194
- 'm' => 'lavender',
195
- 'n' => 'glacier',
196
- 'o' => 'redvelvet',
197
- 'p' => 'verdigris',
198
- 'q' => 'icicle',
199
- 'r' => 'onyx',
200
- 's' => 'hyacinth',
201
- 't' => 'martian',
202
- 'u' => 'hotcocoa',
203
- 'v' => 'shamrock',
204
- 'w' => 'firstblush',
205
- 'x' => ''
206
- }
207
- },
208
- colorsecondary: {
209
- genes: '20-23',
210
- name: 'Highlight Color', code: 'HC', ## color2 / colorsecondary / sec color / pattern color
211
- kai: {
212
- '1' => 'cyborg',
213
- '2' => 'springcrocus',
214
- '3' => 'egyptiankohl',
215
- '4' => 'poisonberry',
216
- '5' => 'lilac',
217
- '6' => 'apricot',
218
- '7' => 'royalpurple',
219
- '8' => 'padparadscha',
220
- '9' => 'swampgreen',
221
- 'a' => 'violet',
222
- 'b' => 'scarlet',
223
- 'c' => 'barkbrown',
224
- 'd' => 'coffee',
225
- 'e' => 'lemonade',
226
- 'f' => 'chocolate',
227
- 'g' => 'butterscotch',
228
- 'h' => 'ooze',
229
- 'i' => 'safetyvest',
230
- 'j' => 'turtleback',
231
- 'k' => 'rosequartz',
232
- 'm' => 'wolfgrey',
233
- 'n' => 'cerulian',
234
- 'o' => 'skyblue',
235
- 'p' => 'garnet',
236
- 'q' => 'peppermint',
237
- 'r' => 'universe',
238
- 's' => 'royalblue',
239
- 't' => 'mertail',
240
- 'u' => 'inflatablepool',
241
- 'v' => 'pearl',
242
- 'w' => 'prairierose',
243
- 'x' => ''
244
- }
245
- },
246
- colortertiary: {
247
- genes: '24-27',
248
- name: 'Accent Color', code: 'AC', ## color3 / colortertiary
249
- kai: {
250
- '1' => 'belleblue',
251
- '2' => 'sandalwood',
252
- '3' => 'peach',
253
- '4' => 'icy',
254
- '5' => 'granitegrey',
255
- '6' => 'cashewmilk',
256
- '7' => 'kittencream',
257
- '8' => 'emeraldgreen',
258
- '9' => 'kalahari',
259
- 'a' => 'shale',
260
- 'b' => 'purplehaze',
261
- 'c' => 'hanauma',
262
- 'd' => 'azaleablush',
263
- 'e' => 'missmuffett',
264
- 'f' => 'morningglory',
265
- 'g' => 'frosting',
266
- 'h' => 'daffodil',
267
- 'i' => 'flamingo',
268
- 'j' => 'buttercup',
269
- 'k' => 'bloodred',
270
- 'm' => 'atlantis',
271
- 'n' => 'summerbonnet',
272
- 'o' => 'periwinkle',
273
- 'p' => 'patrickstarfish',
274
- 'q' => 'seafoam',
275
- 'r' => 'cobalt',
276
- 's' => 'mallowflower',
277
- 't' => 'mintmacaron',
278
- 'u' => 'sully',
279
- 'v' => 'fallspice',
280
- 'w' => 'dreamboat',
281
- 'x' => ''
282
- }
283
- },
284
- wild: {
285
- genes: '28-31',
286
- name: 'Wild Element', code: 'WE',
287
- kai: {
288
- 'h' => 'littlefoot',
289
- 'i' => 'elk',
290
- 'j' => 'ducky',
291
- 'k' => 'trioculus',
292
- 'm' => 'daemonwings',
293
- 'n' => 'featherbrain',
294
- 'o' => 'flapflap',
295
- 'p' => 'daemonhorns',
296
- 'q' => 'dragontail',
297
- 'r' => 'aflutter',
298
- 's' => 'foghornpawhorn',
299
- 't' => 'unicorn',
300
- 'u' => 'dragonwings',
301
- 'v' => 'alicorn',
302
- 'w' => 'wyrm',
303
- 'x' => ''
304
- }
305
- },
306
- mouth: {
307
- genes: '32-35',
308
- name: 'Mouth', code: 'MO',
309
- kai: {
310
- '1' => 'whixtensions',
311
- '2' => 'wasntme',
312
- '3' => 'wuvme',
313
- '4' => 'gerbil',
314
- '5' => 'confuzzled',
315
- '6' => 'impish',
316
- '7' => 'belch',
317
- '8' => 'rollercoaster',
318
- '9' => 'beard',
319
- 'a' => 'pouty',
320
- 'b' => 'saycheese',
321
- 'c' => 'grim',
322
- 'd' => 'fangtastic',
323
- 'e' => 'moue',
324
- 'f' => 'happygokitty',
325
- 'g' => 'soserious',
326
- 'h' => 'cheeky',
327
- 'i' => 'starstruck',
328
- 'j' => 'samwise',
329
- 'k' => 'ruhroh',
330
- 'm' => 'dali',
331
- 'n' => 'grimace',
332
- 'o' => 'majestic',
333
- 'p' => 'tongue',
334
- 'q' => 'yokel',
335
- 'r' => 'topoftheworld',
336
- 's' => 'neckbeard',
337
- 't' => 'satiated',
338
- 'u' => 'walrus',
339
- 'v' => 'struck',
340
- 'w' => 'delite',
341
- 'x' => ''
342
- }
343
- },
344
- environment: {
345
- genes: '36-39',
346
- name: 'Environment', code: 'EN',
347
- kai: {
348
- 'h' => 'salty',
349
- 'i' => 'dune',
350
- 'j' => 'juju',
351
- 'k' => 'tinybox',
352
- 'm' => 'myparade',
353
- 'n' => 'finalfrontier',
354
- 'o' => 'metime',
355
- 'p' => 'drift',
356
- 'q' => 'secretgarden',
357
- 'r' => 'frozen',
358
- 's' => 'roadtogold',
359
- 't' => 'jacked',
360
- 'u' => 'floorislava',
361
- 'v' => 'prism',
362
- 'w' => 'junglebook',
363
- 'x' => ''
364
- }
365
- },
366
- secret: {
367
- genes: '40-43',
368
- name: 'Secret Y Gene', code: 'SE', ## todo: change to Y Gene or Y (see https://guide.cryptokitties.co/guide/cat-features/genes)
369
- kai: { }
370
- },
371
- prestige: {
372
- genes: '44-47',
373
- name: 'Purrstige', code: 'PU',
374
- kai: { }
375
- ## prune, furball, duckduckcat, or thatsawrap - more like fancies (not really traits)
3
+
4
+ class Trait
5
+
6
+ def self.traits_by_name() @@traits_by_name ||= {}; end
7
+ def self.traits_by_code() @@traits_by_code ||= {}; end
8
+
9
+ def self.find_by_code( code )
10
+ ## note: allow string AND symbols (thus, use .to_s)
11
+ ## e.g. allow 'FU01', 'fu01', :fu01, :FU01, etc.
12
+ @@traits_by_code[ code.upcase.to_s ]
13
+ end
14
+
15
+ def self.find_by_name( name )
16
+ ## note: allow string AND symbols (thus, use .to_s !!!)
17
+ ## note: downcase name e.g. allow Savannah too (not just savannah)
18
+ @@traits_by_name[ name.downcase.to_s ]
19
+ end
20
+
21
+ ## add "generic" convenience find helper
22
+ def self.find_by( **kwargs )
23
+ if kwargs[ :name ]
24
+ find_by_name( kwargs[ :name ] )
25
+ elsif kwargs[ :code ]
26
+ find_by_code( kwargs[ :code] )
27
+ else
28
+ ## todo/fix: throw argument except!!!
29
+ nil
30
+ end
31
+ end
32
+
33
+
34
+
35
+ attr_accessor :type,
36
+ :name,
37
+ :kai # kai char e.g. '1', '2', etc.
38
+
39
+ def initialize( **kwargs )
40
+ update( kwargs )
41
+ end
42
+
43
+ def update( **kwargs )
44
+ kwargs.each do |name,value|
45
+ send( "#{name}=", value ) ## use "regular" plain/classic attribute setter
46
+ end
47
+ self ## return self for chaining
48
+ end
49
+
50
+ def num() Kai::NUMBER[@kai]; end
51
+ def code() @type.code + Kai::CODE[@kai]; end
52
+ end # class Trait
53
+
54
+
55
+
56
+ class TraitType
57
+
58
+ def self.trait_types_by_key() @@trait_types_by_key ||= {}; end
59
+ def self.trait_types_by_code() @@trait_types_by_code ||= {}; end
60
+ def self.trait_types_by_name() @@trait_types_by_name ||= {}; end
61
+
62
+ # quick hack - map copycats keys to (internal) cryptokitties trait type keys
63
+ # note: all keys are the same except:
64
+ ALT_TRAIT_TYPE_KEYS =
65
+ {
66
+ # :colorprimary => :color1,
67
+ # :colorsecondary => :color2,
68
+ # :colortertiary => :color3
69
+
70
+ :color1 => :colorprimary,
71
+ :color2 => :colorsecondary,
72
+ :color3 => :colortertiary
376
73
  }
377
- }
74
+
75
+
76
+ def self.find_by_key( key )
77
+ ## note: allow string AND symbols (thus, use .to_sym !!!)
78
+ ## note: allow known alternative mappings/key (e.g. "internal" cryptokitties keys if different)
79
+ key = ALT_TRAIT_TYPE_KEYS[ key.to_sym ] if ALT_TRAIT_TYPE_KEYS[ key.to_sym ]
80
+
81
+ @@trait_types_by_key[ key.to_sym ]
82
+ end
83
+
84
+ def self.find_by_code( code )
85
+ ## note: allow string AND symbols (thus, use .to_s)
86
+ ## e.g. allow 'FU', 'fu', :fu, :FU, etc.
87
+ @@trait_types_by_code[ code.upcase.to_s ]
88
+ end
89
+
90
+ ALT_TRAIT_TYPE_NAMES =
91
+ {
92
+ 'body' => 'fur',
93
+ 'eyes' => 'eye shape',
94
+ 'eye type' => 'eye shape',
95
+ 'body color' => 'base color',
96
+ 'primary color' => 'base color',
97
+ 'base colour' => 'base color', # british (canadian) spelling
98
+ 'secondary color' => 'highlight color',
99
+ 'sec color' => 'highlight color',
100
+ 'pattern color' => 'highlight color',
101
+ 'highlight colour' => 'highlight color', # british (canadian) spelling
102
+ 'tertiary color' => 'accent color',
103
+ 'accent colour' => 'accent color', # british (canadian) spelling
104
+ 'wild' => 'wild element',
105
+ 'secret' => 'secret y gene'
106
+ }
107
+
108
+ def self.find_by_name( name )
109
+ ## note: downcase name e.g. allow fur too (not just Fur)
110
+ ## note: allow known alternative mappings/key (e.g. "internal" cryptokitties keys if different)
111
+ name = ALT_TRAIT_TYPE_NAMES[ name.downcase ] if ALT_TRAIT_TYPE_NAMES[ name.downcase ]
112
+
113
+ @@trait_types_by_name[ name.downcase ]
114
+ end
115
+
116
+
117
+ ## add "generic" convenience find helper
118
+ def self.find_by( **kwargs )
119
+ if kwargs[ :key ]
120
+ find_by_key( kwargs[ :key ] )
121
+ elsif kwargs[ :code ]
122
+ find_by_code( kwargs[ :code ] )
123
+ elsif kwargs[ :name ]
124
+ find_by_name( kwargs[ :name ] )
125
+ else
126
+ ## todo/fix: throw argument except!!!
127
+ nil
128
+ end
129
+ end
130
+
131
+ def self.each
132
+ @@trait_types_by_key.each do |(key,type)|
133
+ yield( type )
134
+ end
135
+ end
136
+
137
+ def self.each_with_index
138
+ @@trait_types_by_key.each_with_index do |(key,type),i|
139
+ yield( type,i )
140
+ end
141
+ end
142
+
143
+
144
+ attr_accessor :key,
145
+ :name,
146
+ :code,
147
+ :genes,
148
+ :traits, ## array of 32 traits
149
+ :cattributes ## array of 29/31/x cattributes
150
+
151
+ def initialize( **kwargs )
152
+ update( kwargs )
153
+ end
154
+
155
+ def update( **kwargs )
156
+ kwargs.each do |name,value|
157
+ send( "#{name}=", value ) ## use "regular" plain/classic attribute setter
158
+ end
159
+ self ## return self for chaining
160
+ end
161
+
162
+ def [](key)
163
+ if key.is_a? Integer ## assume 0,1,2,3,.. index
164
+ traits[ key ]
165
+ elsif key.size == 2 && key =~ /^[0-9]{2}$/ ## assume code e.g. '00', '01', .. etc.
166
+ traits[ key.to_i(10) ]
167
+ else ## assume kai char
168
+ traits[ Kai::NUMBER[key] ]
169
+ end
170
+ end
171
+
172
+
173
+ TRAITS.reduce( TraitType.trait_types_by_key ) do |types, (key, h)|
174
+
175
+ type = TraitType.new(
176
+ key: key,
177
+ name: h[:name], # e.g. 'Fur'
178
+ code: h[:code], # e.g. 'FU'
179
+ genes: h[:genes] # e.g. '0-3'
180
+ )
181
+
182
+ # auto-fill kai mapping
183
+ ## note: to keep "insertion" order - recreate new hash mapping
184
+ hash_kai = h[:kai]
185
+ hash_kai2 = {}
186
+ Kai::ALPHABET.each do |kai|
187
+ name = hash_kai[kai]
188
+ if name.nil?
189
+ ## puts "#{key} - #{kai} is missing"
190
+ ## hash_kai2[kai] = "#{key}_#{kai}"
191
+ hash_kai2[kai] = nil
192
+ elsif name.empty?
193
+ ## puts "#{key} - #{kai} is empty"
194
+ ## hash_kai2[kai] = "#{key}_#{kai}"
195
+ hash_kai2[kai] = nil
196
+ else
197
+ hash_kai2[kai] = name
198
+ end
199
+ end
200
+ h[:kai] = hash_kai2
201
+
202
+ type.traits = h[:kai].reduce([]) do |traits, (kai,name)| # array of 32 traits
203
+ trait = Trait.new(
204
+ type: type,
205
+ name: name,
206
+ kai: kai
207
+ )
208
+
209
+ ## (auto-)add traits for lookup by name, code, etc. via (hash) mapping
210
+ Trait.traits_by_name[ trait.name.downcase ] = trait if trait.name ## note: traits without name (nil) exist!!
211
+ Trait.traits_by_code[ trait.code ] = trait
212
+
213
+ traits << trait
214
+ traits
215
+ end
216
+
217
+ types[key] = type
218
+ types
219
+ end
220
+
221
+ TraitType.trait_types_by_key.reduce( TraitType.trait_types_by_code ) do |types, (key, type)|
222
+ types[type.code] = type
223
+ types
224
+ end
225
+
226
+ TraitType.trait_types_by_key.reduce( TraitType.trait_types_by_name ) do |types, (key, type)|
227
+ ## note: downcase Fur to fur and so on (for case insensitive match)
228
+ types[type.name.downcase] = type
229
+ types
230
+ end
231
+
232
+ end # class TraitType
233
+
234
+
235
+
236
+
237
+ class Traits
238
+ def self.[]( key )
239
+
240
+ ## todo:
241
+ ## add lookup trait type by alt_names (string)
242
+ ## add lookup trait type by alt_keys (symbol)
243
+
244
+ ## check for codes e.g. FU, PA, ... (or fu, pa,...).
245
+ if key.size == 2 && key =~ /^[A-Za-z]{2}$/
246
+ TraitType.find_by_code( key )
247
+ ## check for codes e.g. FU00, FU01 ... (or fu00, fu01, ...)
248
+ elsif key.size == 4 && key =~ /^[A-Za-z]{2}[0-9]{2}$/
249
+ Trait.find_by_code( key )
250
+ else
251
+ if key.is_a? Symbol ## e.g. :body, :pattern, etc.
252
+ t = TraitType.find_by_key( key )
253
+ t = Trait.find_by_name( key ) if t.nil? ## try trait name next - why? why not?
254
+ t
255
+ else ## assume string
256
+ t = TraitType.find_by_name( key )
257
+ t = Trait.find_by_name( key ) if t.nil? ## try trait name next
258
+ t
259
+ end
260
+ end
261
+ end
262
+
263
+ def self.each() TraitType.each { |type| yield(type) }; end
264
+ def self.each_with_index() TraitType.each_with_index { |type,i| yield(type,i) }; end
265
+ end # class Traits