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

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}