traits.gno
4.35 Kb · 181 lines
1package nft
2
3import (
4 "crypto/sha256"
5 "errors"
6)
7
8var (
9 beardColorNames = [...]string{"White", "Grey", "Ginger", "Brown"}
10 beardShapeNames = [...]string{"None", "Long", "Pointy", "Round"}
11 backgroundNames = [...]string{"Sky", "Sand", "Mint", "Lilac", "Blush"}
12 glassesNames = [...]string{"None", "Black", "Gold", "Red", "Teal"}
13 hatNames = [...]string{"Red", "Blue", "Green", "Purple", "Orange"}
14 skinNames = [...]string{"Light", "Medium", "Deep"}
15 tunicNames = [...]string{"Sky", "Leaf", "Clay", "Rose", "Sun"}
16)
17
18// Attribute is a named trait of a gnome.
19type Attribute struct {
20 Type string
21 Value string
22}
23
24// Traits describe a gnome.
25type Traits struct {
26 Skin uint8
27 Hat uint8
28 BeardColor uint8
29 Tunic uint8
30 Background uint8
31 HatShape uint8 // 0 pointy, 1 bent
32 Flip uint8 // 0 or 1, no effect on pointy hats
33 Glasses uint8
34 Beard uint8
35}
36
37// NewTraits derives the traits of the gnome from a seed.
38// A single seed always generate the same traits.
39// Use `Validate()` to check traits before drawing.
40func NewTraits(seed string) Traits {
41 data := sha256.Sum256([]byte(seed))
42 t := Traits{
43 Skin: data[0] % 3,
44 Hat: data[1] % 5,
45 BeardColor: data[2] % 4,
46 Tunic: data[3] % 5,
47 Background: data[4] % 5,
48 Flip: data[6] & 1,
49 }
50
51 // Gnome with bent hat, about 35%
52 if data[5] < 90 {
53 t.HatShape = 1
54 }
55
56 // Gnome with glasses, about 30%
57 if data[7] < 77 {
58 t.Glasses = data[8]%4 + 1
59 }
60
61 // Gnome without beard is a rare trait, about 4%
62 if data[9] >= 10 {
63 t.Beard = data[10]%3 + 1
64 }
65 return t
66}
67
68// Validate checks that all the traits are in range.
69func (t Traits) Validate() error {
70 switch {
71 case t.Skin > 2:
72 return errors.New("invalid skin")
73 case t.Hat > 4:
74 return errors.New("invalid hat color")
75 case t.BeardColor > 3:
76 return errors.New("invalid beard color")
77 case t.Tunic > 4:
78 return errors.New("invalid tunic")
79 case t.Background > 4:
80 return errors.New("invalid background")
81 case t.HatShape > 1:
82 return errors.New("invalid hat shape")
83 case t.Flip > 1:
84 return errors.New("invalid flip")
85 case t.Glasses > 4:
86 return errors.New("invalid glasses")
87 case t.Beard > 3:
88 return errors.New("invalid beard")
89 }
90 return nil
91}
92
93// Normalized returns the traits with the flip cleared when it has no effect.
94// Two traits that draw the same gnome have the same normalized traits, so
95// this is the value to compare and to keep when gnomes must be different.
96func (t Traits) Normalized() Traits {
97 // Clear trait when hat is pointy.
98 if t.HatShape == 0 {
99 t.Flip = 0
100 }
101 return t
102}
103
104// Attributes returns the traits of a gnome with their names.
105// It panics when traits are not valid.
106func (t Traits) Attributes() []Attribute {
107 if err := t.Validate(); err != nil {
108 panic(err.Error())
109 }
110
111 hat := "Pointed"
112 if t.HatShape == 1 {
113 hat = "Bent tip (right)"
114 if t.Flip == 1 {
115 hat = "Bent tip (left)"
116 }
117 }
118
119 return []Attribute{
120 {"Skin", skinNames[t.Skin]},
121 {"Hat color", hatNames[t.Hat]},
122 {"Hat", hat},
123 {"Beard color", beardColorNames[t.BeardColor]},
124 {"Beard", beardShapeNames[t.Beard]},
125 {"Tunic", tunicNames[t.Tunic]},
126 {"Glasses", glassesNames[t.Glasses]},
127 {"Background", backgroundNames[t.Background]},
128 }
129}
130
131// Pack packs the traits in 20 bits.
132func Pack(t Traits) uint32 {
133 // Bits:
134 // 0-1 skin
135 // 2-4 hat
136 // 5-6 beard color
137 // 7-9 tunic
138 // 10-12 background
139 // 13 hat shape
140 // 14 flip
141 // 15-17 glasses
142 // 18-19 beard
143 return uint32(t.Skin) |
144 uint32(t.Hat)<<2 |
145 uint32(t.BeardColor)<<5 |
146 uint32(t.Tunic)<<7 |
147 uint32(t.Background)<<10 |
148 uint32(t.HatShape)<<13 |
149 uint32(t.Flip)<<14 |
150 uint32(t.Glasses)<<15 |
151 uint32(t.Beard)<<18
152}
153
154// Unpack unpacks the traits.
155// It does not check the values, use FromPacked for untrusted input.
156func Unpack(v uint32) Traits {
157 return Traits{
158 Skin: uint8(v & 3),
159 Hat: uint8((v >> 2) & 7),
160 BeardColor: uint8((v >> 5) & 3),
161 Tunic: uint8((v >> 7) & 7),
162 Background: uint8((v >> 10) & 7),
163 HatShape: uint8((v >> 13) & 1),
164 Flip: uint8((v >> 14) & 1),
165 Glasses: uint8((v >> 15) & 7),
166 Beard: uint8((v >> 18) & 3),
167 }
168}
169
170// FromPacked unpacks traits and checks them.
171func FromPacked(v uint32) (Traits, error) {
172 if v>>20 != 0 {
173 return Traits{}, errors.New("invalid packed traits")
174 }
175
176 t := Unpack(v)
177 if err := t.Validate(); err != nil {
178 return Traits{}, err
179 }
180 return t, nil
181}