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

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}