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.

grid.gno

3.62 Kb · 165 lines
  1package nft
  2
  3const (
  4	gridW = 24
  5	gridH = 32
  6	half  = 12 // columns of the left half, where rows are written
  7)
  8
  9// span is a horizontal range of pixels, x0 to x1 inclusive.
 10type span struct{ y, x0, x1 int }
 11
 12// BuildGrid returns the pixel grid of a gnome, as 32 rows of 24 palette keys ('.' is transparent).
 13// Traits must be valid.
 14func BuildGrid(t Traits) []string {
 15	g := buildGrid(t)
 16
 17	rows := make([]string, gridH)
 18	for y := 0; y < gridH; y++ {
 19		row := make([]byte, gridW)
 20		for x := 0; x < gridW; x++ {
 21			c := g[y*gridW+x]
 22			if c == 0 {
 23				c = '.'
 24			}
 25			row[x] = c
 26		}
 27		rows[y] = string(row)
 28	}
 29	return rows
 30}
 31
 32// buildGrid draws all the layers of a gnome. The grid has a byte per pixel, row by row, and 0 is transparent.
 33func buildGrid(t Traits) []byte {
 34	if err := t.Validate(); err != nil {
 35		panic("settlers: " + err.Error())
 36	}
 37
 38	g := make([]byte, gridW*gridH)
 39
 40	draw(g, body, 19)
 41	if t.Beard == 0 {
 42		draw(g, head+headNoBeard, 13)
 43	} else {
 44		switch t.Beard {
 45		case 1:
 46			draw(g, beardLong, 19)
 47		case 2:
 48			draw(g, beardPointy, 19)
 49		default:
 50			draw(g, beardRound, 19)
 51		}
 52		draw(g, head+headBeard, 13)
 53	}
 54
 55	if t.Glasses > 0 {
 56		for _, p := range glassesFrame {
 57			put(g, p[0], p[1], 'g')
 58		}
 59		for _, p := range glassesEyes {
 60			put(g, p[0], p[1], 'e')
 61		}
 62	}
 63
 64	if t.HatShape == 0 {
 65		draw(g, hatTall, 0)
 66	} else {
 67		drawBentHat(g, t.Flip == 1)
 68	}
 69	draw(g, brim, 11)
 70	draw(g, boots, 28)
 71	return g
 72}
 73
 74// draw draws rows, that are the left half of the sprite, starting at row y0. The right half is
 75// the left half mirrored, where light tones are swapped by their shaded twin.
 76func draw(g []byte, rows string, y0 int) {
 77	for i := 0; i*half < len(rows); i++ {
 78		y := y0 + i
 79		if y < 0 || y >= gridH {
 80			continue
 81		}
 82
 83		row := rows[i*half : (i+1)*half]
 84		for j := 0; j < half; j++ {
 85			c := row[j]
 86			if c != '.' {
 87				g[y*gridW+j] = c
 88				g[y*gridW+gridW-1-j] = swapTable[c]
 89			}
 90		}
 91	}
 92}
 93
 94func put(g []byte, x, y int, c byte) {
 95	if x >= 0 && x < gridW && y >= 0 && y < gridH {
 96		g[y*gridW+x] = c
 97	}
 98}
 99
100// drawBentHat draws the hat with the tip that falls to the right, or to the left when flipped.
101//
102// It is written without function calls (no closures or helpers), because they are the most
103// expensive thing in Gno. The fill has a border of one cell, so neighbors never need a bounds check.
104func drawBentHat(g []byte, flip bool) {
105	const (
106		rows   = coneY0 + len(coneK) + 1 // rows of the hat, one more than the cone for its outline
107		stride = gridW + 2
108	)
109	var fill [(rows + 2) * stride]bool // No maps are used to keep it deterministic
110
111	for i, k := range coneK {
112		for x := half - k; x < half+k; x++ {
113			if flip {
114				fill[(coneY0+i+1)*stride+gridW-x] = true // mirrored column gridW-1-x, plus the border
115			} else {
116				fill[(coneY0+i+1)*stride+x+1] = true
117			}
118		}
119	}
120	for _, s := range tailSpans {
121		for x := s.x0; x <= s.x1; x++ {
122			if flip {
123				fill[(s.y+1)*stride+gridW-x] = true
124			} else {
125				fill[(s.y+1)*stride+x+1] = true
126			}
127		}
128	}
129
130	for y := 0; y < rows; y++ {
131		for x := 0; x < gridW; x++ {
132			i := (y+1)*stride + x + 1
133			if !fill[i] {
134				continue
135			}
136			left, right, up, down := fill[i-1], fill[i+1], fill[i-stride], fill[i+stride]
137
138			// Shading
139			c := byte('H')
140			if !left {
141				if y%2 == 1 {
142					c = 'h'
143				}
144			} else if !right || !down {
145				c = 'j'
146			}
147			g[y*gridW+x] = c
148
149			// Outline. It only touches pixels that are not part of the hat, so it doesn't matter
150			// that it is done in the same pass as the shading.
151			if !left && x > 0 {
152				g[y*gridW+x-1] = 'o'
153			}
154			if !right && x < gridW-1 {
155				g[y*gridW+x+1] = 'o'
156			}
157			if !up && y > 0 {
158				g[(y-1)*gridW+x] = 'o'
159			}
160			if !down {
161				g[(y+1)*gridW+x] = 'o'
162			}
163		}
164	}
165}