Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}