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.

art.gno

8.33 Kb · 265 lines
  1package pixelgnomes
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"gno.land/p/nt/ufmt/v0"
  8)
  9
 10// The drawing. A port of the Classic trait sheet: a 20×28 gnome sprite on a
 11// 32×32 canvas, with nine traits layered on top. Integer arithmetic only.
 12
 13const (
 14	side       = 32
 15	traitCount = 9
 16	ink        = "#1b1b22"
 17	white      = "#ffffff"
 18)
 19
 20type option struct{ name, color string }
 21
 22var traitNames = [traitCount]string{"hat", "pattern", "beard", "coat", "skin", "eyes", "accessory", "friend", "background"}
 23
 24var (
 25	hats     = []option{{"red", "#d63c3c"}, {"blue", "#3867d6"}, {"green", "#4f9d4a"}, {"purple", "#8e4fc9"}, {"orange", "#f07f2d"}, {"pink", "#ff7fb5"}, {"teal", "#2aa198"}, {"navy", "#283a8f"}, {"ice", "#6fd3e8"}, {"berry", "#a33b5c"}, {"slate", "#5c6b73"}, {"gold", "#f2c230"}}
 26	patterns = []option{{"plain", ""}, {"stripes", ""}, {"dots", ""}, {"star", ""}}
 27	beards   = []option{{"snow", "#f4f1ea"}, {"silver", "#cfd6dc"}, {"brown", "#8a5a3b"}, {"ginger", "#d9793b"}, {"black", "#2d2a2a"}, {"blond", "#f2d27a"}, {"lilac", "#d8c4f2"}, {"frost", "#9fd0ff"}}
 28	coats    = []option{{"blue", "#3b6fd8"}, {"red", "#c9443a"}, {"moss", "#3f8f4f"}, {"leather", "#7a4e2d"}, {"mustard", "#f2b53a"}, {"denim", "#23406e"}, {"teal", "#2aa198"}, {"mint", "#7ed9c3"}, {"charcoal", "#2c2f36"}, {"violet", "#6a3fa0"}}
 29	skins    = []option{{"light", "#f6d3b4"}, {"peach", "#eab28a"}, {"tan", "#c98b5e"}, {"brown", "#95603f"}, {"deep", "#6b422c"}}
 30	eyeTypes = []option{{"open", ""}, {"happy", ""}, {"sleepy", ""}, {"wink", ""}}
 31	accs     = []option{{"none", ""}, {"rosy cheeks", ""}, {"scarf", ""}, {"glasses", ""}, {"beard flower", ""}}
 32	friends  = []option{{"none", ""}, {"mushroom", ""}, {"flower", ""}, {"lantern", ""}, {"star", ""}, {"snail", ""}}
 33	bgs      = []option{{"mint", "#bfe8d6"}, {"lemon", "#fbeaa0"}, {"peach", "#ffd6c2"}, {"sky", "#c9e6ff"}, {"mist", "#d9ecf2"}, {"lavender", "#ddd5f5"}, {"sand", "#f3dfb3"}, {"frost", "#cfe6f5"}, {"butter", "#ffe3a3"}, {"blush", "#ffd6e8"}, {"dusk", "#1d2a4a"}, {"midnight", "#141a33"}, {"plum night", "#2a1f4a"}, {"deep night", "#1c2447"}, {"space", "#0f1633"}}
 34)
 35
 36func pools() [traitCount][]option {
 37	return [traitCount][]option{hats, patterns, beards, coats, skins, eyeTypes, accs, friends, bgs}
 38}
 39
 40func optionName(trait, idx int) string { return pools()[trait][idx].name }
 41
 42const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
 43
 44func traitsOf(id int) [traitCount]int {
 45	var t [traitCount]int
 46	row := traitTable[(id-1)*traitCount : id*traitCount]
 47	p := pools()
 48	for i := 0; i < traitCount; i++ {
 49		v := strings.IndexByte(digits, row[i])
 50		if v < 0 || v >= len(p[i]) {
 51			panic("gnomes: corrupt trait table")
 52		}
 53		t[i] = v
 54	}
 55	return t
 56}
 57
 58var body = []string{
 59	"...........H........",
 60	"..........HHh.......",
 61	".........HHHh.......",
 62	".........HHHHh......",
 63	"........HHHHHh......",
 64	"........HHHHHHh.....",
 65	".......HHHHHHHh.....",
 66	"......HHHHHHHHHh....",
 67	".....HHHHHHHHHHHh...",
 68	"....HHHHHHHHHHHHHh..",
 69	"...hhhhhhhhhhhhhhhh.",
 70	".....SSSSSSSSSSSS...",
 71	".....S..SSSSS..SS...",
 72	".....S..SSSSS..SS...",
 73	".....RSSSSNNSSSSR...",
 74	"....BBSSSSSSSSSSBB..",
 75	"....BBBBBBBBBBBBBB..",
 76	"...BBBBBBBBBBBBBBBB.",
 77	"..CBBBBBBBBBBBBBBBBC",
 78	"..CCBBBBBBBBBBBBBBCC",
 79	"..SCCCBBBBBBBBBBCCCS",
 80	"...CCCCbBBBBBBbCCCC.",
 81	"...LLLLLLBBBBLLLLLL.",
 82	"...CCCCCCCBBCCCCCCC.",
 83	"...PPPPPPPPPPPPPPPP.",
 84	"...PPPPPPP..PPPPPPP.",
 85	"..OOOOOOOO..OOOOOOOO",
 86	"..OOOOOOOO..OOOOOOOO",
 87}
 88
 89// Eyes fill the two 2×2 holes: row, col, paint.
 90var eyeCells = map[string][][3]int{
 91	"open":   {{12, 6, 'W'}, {12, 7, 'E'}, {13, 6, 'E'}, {13, 7, 'E'}, {12, 13, 'W'}, {12, 14, 'E'}, {13, 13, 'E'}, {13, 14, 'E'}},
 92	"happy":  {{12, 6, 'E'}, {12, 7, 'S'}, {13, 6, 'S'}, {13, 7, 'E'}, {12, 13, 'S'}, {12, 14, 'E'}, {13, 13, 'E'}, {13, 14, 'S'}},
 93	"sleepy": {{12, 6, 'S'}, {12, 7, 'S'}, {13, 6, 'E'}, {13, 7, 'E'}, {12, 13, 'S'}, {12, 14, 'S'}, {13, 13, 'E'}, {13, 14, 'E'}},
 94	"wink":   {{12, 6, 'W'}, {12, 7, 'E'}, {13, 6, 'E'}, {13, 7, 'E'}, {12, 13, 'S'}, {12, 14, 'S'}, {13, 13, 'E'}, {13, 14, 'E'}},
 95}
 96
 97type friendSprite struct {
 98	rows []string
 99	x, y int
100}
101
102var friendSprites = map[string]friendSprite{
103	"mushroom": {[]string{"..RRR..", ".RWRRR.", "RRRRWRR", "...T...", "..TTT.."}, 0, 25},
104	"star":     {[]string{".Y.", "YYY", ".Y."}, 17, 0},
105	"lantern":  {[]string{".K.", "KYK", "KYK", ".K."}, 26, 22},
106	"flower":   {[]string{".F.", "FYF", ".F.", ".G."}, 6, 19},
107	"snail":    {[]string{"..KKK", ".KYYK", "TTTKK"}, 26, 28},
108}
109
110var friendPaint = map[byte]string{'R': "#e8453c", 'W': white, 'T': "#efe6d2", 'Y': "#ffd84a", 'K': "#5a4636", 'F': "#ff8fb8", 'G': "#4c9a52"}
111
112type canvas struct{ px [side * side]string }
113
114func (c *canvas) put(x, y int, col string) {
115	if x >= 0 && y >= 0 && x < side && y < side {
116		c.px[y*side+x] = col
117	}
118}
119
120const hexDigits = "0123456789abcdef"
121
122// shade scales a #rrggbb colour by pct percent.
123func shade(hex string, pct int) string {
124	v, err := strconv.ParseUint(hex[1:], 16, 32)
125	if err != nil {
126		panic("gnomes: bad colour " + hex)
127	}
128	out := []byte{'#'}
129	for _, shift := range []uint{16, 8, 0} {
130		ch := int((v>>shift)&255) * pct / 100
131		if ch > 255 {
132			ch = 255
133		}
134		out = append(out, hexDigits[ch>>4], hexDigits[ch&15])
135	}
136	return string(out)
137}
138
139// Art draws gnome `tid` as an SVG.
140func Art(tid string) string {
141	id := mustID(tid)
142	t := traitsOf(id)
143	hat, beard, coat, skin, bg := hats[t[0]].color, beards[t[2]].color, coats[t[3]].color, skins[t[4]].color, bgs[t[8]]
144	night := t[8] >= 10
145
146	paint := map[byte]string{
147		'H': hat, 'h': shade(hat, 72), 'S': skin, 'R': shade(skin, 88), 'N': shade(skin, 82),
148		'B': beard, 'b': shade(beard, 82), 'C': coat, 'L': "#3a2a1f", 'P': shade(coat, 55),
149		'O': "#2a2220", 'E': ink, 'W': white,
150	}
151	var c canvas
152	const ox, oy = 6, 3
153	sp := func(x, y int, col string) { c.put(ox+x, oy+y, col) }
154
155	for y, row := range body {
156		for x := 0; x < len(row); x++ {
157			if row[x] != '.' {
158				sp(x, y, paint[row[x]])
159			}
160		}
161	}
162	switch patterns[t[1]].name {
163	case "stripes":
164		for y, row := range body {
165			if y == 3 || y == 6 || y == 9 {
166				for x := 0; x < len(row); x++ {
167					if row[x] == 'H' {
168						sp(x, y, shade(hat, 140))
169					}
170				}
171			}
172		}
173	case "dots":
174		for _, d := range [][2]int{{10, 4}, {9, 7}, {12, 6}, {7, 9}, {11, 9}, {14, 8}} {
175			sp(d[0], d[1], white)
176		}
177	case "star":
178		for _, d := range [][2]int{{10, 5}, {9, 6}, {10, 6}, {11, 6}, {10, 7}} {
179			sp(d[0], d[1], "#ffd84a")
180		}
181	}
182	for _, e := range eyeCells[eyeTypes[t[5]].name] {
183		sp(e[1], e[0], paint[byte(e[2])])
184	}
185	switch accs[t[6]].name {
186	case "rosy cheeks":
187		sp(5, 14, "#ff7f96")
188		sp(6, 14, "#ff9fae")
189		sp(15, 14, "#ff9fae")
190		sp(16, 14, "#ff7f96")
191	case "glasses":
192		const frame = "#3b2f2a"
193		for _, cols := range [][4]int{{5, 6, 7, 8}, {12, 13, 14, 15}} {
194			for _, x := range cols {
195				sp(x, 11, frame)
196				sp(x, 14, frame)
197			}
198			sp(cols[0], 12, frame)
199			sp(cols[0], 13, frame)
200			sp(cols[3], 12, frame)
201			sp(cols[3], 13, frame)
202		}
203		sp(9, 12, frame)
204		sp(10, 12, frame)
205		sp(11, 12, frame)
206	case "scarf":
207		for x := 4; x <= 17; x++ {
208			if x%2 == 1 {
209				sp(x, 15, "#e8b53a")
210			} else {
211				sp(x, 15, "#c7922a")
212			}
213		}
214		for y := 16; y <= 20; y++ {
215			sp(14, y, "#e8b53a")
216			sp(15, y, "#c7922a")
217		}
218	case "beard flower":
219		sp(12, 18, "#ff8fb8")
220		sp(11, 19, "#ff8fb8")
221		sp(13, 19, "#ff8fb8")
222		sp(12, 20, "#ff8fb8")
223		sp(12, 19, "#ffd84a")
224	}
225	if f, ok := friendSprites[friends[t[7]].name]; ok {
226		for y, row := range f.rows {
227			for x := 0; x < len(row); x++ {
228				if col, ok := friendPaint[row[x]]; ok {
229					c.put(f.x+x, f.y+y, col)
230				}
231			}
232		}
233	}
234
235	var b strings.Builder
236	b.WriteString(ufmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" shape-rendering="crispEdges">`, side, side))
237	b.WriteString(`<rect width="32" height="32" fill="` + bg.color + `"/>`)
238	grass := "#6fbf73"
239	if night {
240		grass = "#2a4a3c"
241		for _, s := range [][2]int{{3, 4}, {9, 2}, {27, 5}, {23, 11}, {2, 14}, {29, 16}, {14, 1}} {
242			b.WriteString(ufmt.Sprintf(`<rect x="%d" y="%d" width="1" height="1" fill="#fff6c9"/>`, s[0], s[1]))
243		}
244	}
245	b.WriteString(`<rect y="29" width="32" height="3" fill="` + grass + `"/>`)
246	b.WriteString(`<rect y="29" width="32" height="1" fill="` + shade(grass, 115) + `"/>`)
247	for y := 0; y < side; y++ {
248		x := 0
249		for x < side {
250			col := c.px[y*side+x]
251			if col == "" {
252				x++
253				continue
254			}
255			n := 1
256			for x+n < side && c.px[y*side+x+n] == col {
257				n++
258			}
259			b.WriteString(ufmt.Sprintf(`<rect x="%d" y="%d" width="%d" height="1" fill="%s"/>`, x, y, n, col))
260			x += n
261		}
262	}
263	b.WriteString(`</svg>`)
264	return b.String()
265}