-
Notifications
You must be signed in to change notification settings - Fork 1
/
gameutil.go
346 lines (303 loc) · 8.05 KB
/
gameutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package gridspech
import (
"fmt"
"strings"
)
// Width of the grid.
func (g Grid) Width() int {
return len(g.Tiles)
}
// Height of the grid.
func (g Grid) Height() int {
return len(g.Tiles[0])
}
// TileAt returns a reference to the tile at the coordinate.
func (g Grid) TileAt(x, y int) *Tile {
return &g.Tiles[x][y]
}
// TileAtCoord returns a reference to the tile at the coordinate.
func (g Grid) TileAtCoord(coord TileCoord) *Tile {
return &g.Tiles[coord.X][coord.Y]
}
// NorthOf returns the tile north of t in g.
func (g Grid) NorthOf(t Tile) Tile {
// special behavior if we have an arrow
if t.Data.ArrowNorth {
y := t.Coord.Y
for {
y = (y + 1) % g.Height()
if y == t.Coord.Y {
break
}
if g.TileAt(t.Coord.X, y).Data.Type != TypeHole {
break
}
}
return *g.TileAt(t.Coord.X, y)
}
if t.Coord.Y == g.Height()-1 || t.Data.Type == TypeHole {
return Tile{}
}
return g.Tiles[t.Coord.X][t.Coord.Y+1]
}
// EastOf returns the tile east of t in g.
func (g Grid) EastOf(t Tile) Tile {
// special behavior if we have an arrow
if t.Data.ArrowEast {
x := t.Coord.X
for {
x = (x + 1) % g.Width()
if x == t.Coord.X {
break
}
if g.TileAt(x, t.Coord.Y).Data.Type != TypeHole {
break
}
}
return *g.TileAt(x, t.Coord.Y)
}
if t.Coord.X == g.Width()-1 || t.Data.Type == TypeHole {
return Tile{}
}
return g.Tiles[t.Coord.X+1][t.Coord.Y]
}
// SouthOf returns the tile south of t in g.
func (g Grid) SouthOf(t Tile) Tile {
// special behavior if we have an arrow
if t.Data.ArrowSouth {
y := t.Coord.Y
for {
y = (y + g.Height() - 1) % g.Height()
if y == t.Coord.Y {
break
}
if g.TileAt(t.Coord.X, y).Data.Type != TypeHole {
break
}
}
return *g.TileAt(t.Coord.X, y)
}
if t.Coord.Y == 0 || t.Data.Type == TypeHole {
return Tile{}
}
return g.Tiles[t.Coord.X][t.Coord.Y-1]
}
// WestOf returns the tile west of t in g.
func (g Grid) WestOf(t Tile) Tile {
// special behavior if we have an arrow
if t.Data.ArrowWest {
x := t.Coord.X
for {
x = (x + g.Width() - 1) % g.Width()
if x == t.Coord.X {
break
}
if g.TileAt(x, t.Coord.Y).Data.Type != TypeHole {
break
}
}
return *g.TileAt(x, t.Coord.Y)
}
if t.Coord.X == 0 || t.Data.Type == TypeHole {
return Tile{}
}
return g.Tiles[t.Coord.X-1][t.Coord.Y]
}
func findTile(start Tile, iter func(each Tile) (next Tile, ok bool)) Tile {
last := start
next, ok := iter(start)
for {
if !ok {
return last
}
if next == start {
panic("no tile found")
}
last = next
next, ok = iter(next)
}
}
// NeighborSlice returns a slice of the tiles directly next to t.
// Note that arrows can cause a tile to be its own neighbor, or to
// have a tile appear more than once in the slice.
func (g Grid) NeighborSlice(coord TileCoord) []Tile {
t := *g.TileAtCoord(coord)
var neighbors []Tile
if neighbor := g.NorthOf(t); neighbor.Data.Type != TypeHole {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.EastOf(t); neighbor.Data.Type != TypeHole {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.SouthOf(t); neighbor.Data.Type != TypeHole {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.WestOf(t); neighbor.Data.Type != TypeHole {
neighbors = append(neighbors, neighbor)
}
return neighbors
}
// NeighborSliceWith returns a slice of the tiles directly next to t, such that pred returns true.
// Note that arrows can cause a tile to be its own neighbor, or to
// have a tile appear more than once in the slice.
func (g Grid) NeighborSliceWith(coord TileCoord, pred func(o Tile) bool) []Tile {
t := *g.TileAtCoord(coord)
var neighbors []Tile
if neighbor := g.NorthOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.EastOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.SouthOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
neighbors = append(neighbors, neighbor)
}
if neighbor := g.WestOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
neighbors = append(neighbors, neighbor)
}
return neighbors
}
// NeighborSet returns a set of all tiles directly next to t.
// Note that because this is a TileSet, if a neighbor would appear
// more than once in NeighborSlice(), it will only appear once here.
func (g Grid) NeighborSet(coord TileCoord) TileSet {
t := *g.TileAtCoord(coord)
var ts TileSet
if neighbor := g.NorthOf(t); neighbor.Data.Type != TypeHole {
ts.Add(neighbor)
}
if neighbor := g.EastOf(t); neighbor.Data.Type != TypeHole {
ts.Add(neighbor)
}
if neighbor := g.SouthOf(t); neighbor.Data.Type != TypeHole {
ts.Add(neighbor)
}
if neighbor := g.WestOf(t); neighbor.Data.Type != TypeHole {
ts.Add(neighbor)
}
return ts
}
// NeighborSetWith returns the set of neighbors such that `pred` returns true
func (g Grid) NeighborSetWith(coord TileCoord, pred func(o Tile) bool) TileSet {
t := *g.TileAtCoord(coord)
var ts TileSet
if neighbor := g.NorthOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
ts.Add(neighbor)
}
if neighbor := g.EastOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
ts.Add(neighbor)
}
if neighbor := g.SouthOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
ts.Add(neighbor)
}
if neighbor := g.WestOf(t); neighbor.Data.Type != TypeHole && pred(neighbor) {
ts.Add(neighbor)
}
return ts
}
// TilesWith returns all non-hole tiles such that `pred` returns true.
func (g Grid) TilesWith(pred func(o Tile) bool) TileSet {
var ts TileSet
for _, col := range g.Tiles {
for _, tile := range col {
if tile.Data.Type != TypeHole && pred(tile) {
ts.Add(tile)
}
}
}
return ts
}
// ApplyTileSet will loop through ts and update all tiles
// with the same coordinates to have the same data as the tiles in ts.
func (g Grid) ApplyTileSet(ts TileSet) {
for _, tile := range ts.Slice() {
g.Tiles[tile.Coord.X][tile.Coord.Y] = tile
}
}
// Blob returns all tiles which can form a path to t such that all tiles in the path have the same Color.
func (g Grid) Blob(coord TileCoord) TileSet {
var ts TileSet
g.blobRecur(coord, &ts, func(o Tile) bool { return true })
return ts
}
// BlobWith is similar to Blob, but with a filter function to exclude certain tiles.
func (g Grid) BlobWith(coord TileCoord, filter func(o Tile) bool) TileSet {
var ts TileSet
g.blobRecur(coord, &ts, filter)
return ts
}
func (g Grid) blobRecur(coord TileCoord, ts *TileSet, filter func(o Tile) bool) {
t := *g.TileAtCoord(coord)
ts.Add(t)
neighbors := g.NeighborSetWith(coord, func(o Tile) bool {
return o.Data.Color == t.Data.Color && filter(o)
})
for _, neighbor := range neighbors.Slice() {
if !ts.Has(neighbor) {
g.blobRecur(neighbor.Coord, ts, filter)
}
}
}
func (t Tile) String() string {
return fmt.Sprintf("[%v: %v]", t.Coord, t.Data)
}
func (t TileCoord) String() string {
return fmt.Sprintf("(%d, %d)", t.X, t.Y)
}
func (td TileData) String() string {
if td.Type == TypeHole {
return "_"
}
// format:
// `${t.value}${lock}${sym}${wrapl}${wrapu}${wrapd}${wrapr}`
var sb strings.Builder
sb.WriteByte(byte(td.Color) + '0')
if td.Sticky {
sb.WriteByte('/')
}
switch td.Type {
case TypeBlank:
break
case TypeCrown:
sb.WriteByte('k')
case TypeGoal:
sb.WriteByte('e')
case TypeDot1:
sb.WriteString("m1")
case TypeDot2:
sb.WriteString("m2")
case TypeDot3:
sb.WriteString("m3")
case TypeJoin1:
sb.WriteString("j1")
case TypeJoin2:
sb.WriteString("j2")
default:
panic(fmt.Sprintf("invalid type %d", td.Type))
}
if td.ArrowWest {
sb.WriteByte('<')
}
if td.ArrowNorth {
sb.WriteByte('^')
}
if td.ArrowSouth {
sb.WriteByte('v')
}
if td.ArrowEast {
sb.WriteByte('>')
}
return sb.String()
}
// Clone returns a clone of the grid. Modifications to the new grid will not modify the original grid.
func (g Grid) Clone() Grid {
var newGrid Grid
newGrid.Tiles = make([][]Tile, 0, len(g.Tiles))
newGrid.MaxColors = g.MaxColors
for _, col := range g.Tiles {
newCol := make([]Tile, len(col))
newGrid.Tiles = append(newGrid.Tiles, newCol)
copy(newCol, col)
}
return newGrid
}