-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrules.go
127 lines (112 loc) · 3.45 KB
/
rules.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
package gridspech
import "fmt"
// Valid returns if all tiles in the grid are valid.
func (g Grid) Valid() bool {
for x := 0; x < g.Width(); x++ {
for y := 0; y < g.Height(); y++ {
if !g.ValidTile(TileCoord{X: x, Y: y}) {
return false
}
}
}
return true
}
// ValidTile returns if t is valid in g. If all tiles in g are valid,
// the grid is completed.
func (g Grid) ValidTile(coord TileCoord) bool {
t := *g.TileAtCoord(coord)
switch t.Data.Type {
case TypeHole, TypeBlank:
return true
case TypeGoal:
return g.validGoal(t)
case TypeCrown:
return g.validCrown(t)
case TypeDot1:
return len(g.NeighborSliceWith(t.Coord, func(other Tile) bool {
return other.Data.Color != ColorNone
})) == 1
case TypeDot2:
return len(g.NeighborSliceWith(t.Coord, func(other Tile) bool {
return other.Data.Color != ColorNone
})) == 2
case TypeDot3:
return len(g.NeighborSliceWith(t.Coord, func(other Tile) bool {
return other.Data.Color != ColorNone
})) == 3
case TypeJoin1:
return g.validJoin(t, 1)
case TypeJoin2:
return g.validJoin(t, 2)
default:
panic(fmt.Sprintf("invalid tile type %v", t.Data.Type))
}
}
// the blob of a goal tile should contain a direct path to another goal.
// the way we measure this:
// 1. The blob should contain exactly two goals.
// 2. The goals should have exactly 1 neighbor with the same state.
// 3. All other tiles in the blob should have exactly 2 neighbors with the same state.
func (g Grid) validGoal(start Tile) bool {
blob := g.Blob(start.Coord)
var goals int
for _, t := range blob.Slice() {
if t.Data.Type == TypeGoal {
goals++
// requirement 2: The goals should have exactly 1 neighbor with the same state.
neighbors := g.NeighborSliceWith(t.Coord, func(o Tile) bool {
return t.Data.Color == o.Data.Color
})
if len(neighbors) != 1 {
return false
}
}
// requirement 3: All other tiles in the blob should have exactly 2 neighbors with the same state.
neighborsSameColor := g.NeighborSliceWith(t.Coord, func(o Tile) bool {
return t.Data.Color == o.Data.Color
})
if t.Data.Type != TypeGoal && len(neighborsSameColor) != 2 {
return false
}
}
// requirement 1: The blob should contain exactly two goals.
return goals == 2
}
// crown tiles have the following requirements:
// 1. No other crowns may be in this crown's blob.
// 2. All tiles with the same color must have a crown in its blob.
func (g Grid) validCrown(start Tile) bool {
blob := g.Blob(start.Coord)
// requirement 1: No other crowns may be in this crown's blob.
for _, tile := range blob.Slice() {
if tile.Data.Type == TypeCrown && tile != start {
return false
}
}
crownsWithSameState := g.TilesWith(func(t Tile) bool {
return t.Data.Type == TypeCrown && t.Data.Color == start.Data.Color
})
// set of blobs of all crowns with same color
var crownsBlobSet TileSet
for crown := range crownsWithSameState.Iter() {
crownsBlobSet.Merge(g.Blob(crown.Coord))
}
// set of all tiles with same color
stateSet := g.TilesWith(func(t Tile) bool {
return t.Data.Type != TypeHole && t.Data.Color == start.Data.Color
})
// requirement 2: All tiles with the same color must have a crown in its blob.
return crownsBlobSet.Eq(stateSet)
}
func (g Grid) validJoin(t Tile, n int) bool {
var found int
for _, blobTile := range g.Blob(t.Coord).Slice() {
if blobTile.Data.Type != TypeHole && blobTile.Data.Type != TypeBlank {
found++
if found > n+1 {
return false
}
}
}
return found == n+1
}