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}