-
Notifications
You must be signed in to change notification settings - Fork 2
/
board_utils.go
201 lines (159 loc) · 5.13 KB
/
board_utils.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
package sgf
import (
"fmt"
)
// Stones returns all stones in the group at point p, in arbitrary order. The
// argument should be an SGF coordinate, e.g. "dd".
func (self *Board) Stones(p string) []string {
colour := self.Get(p)
if colour == EMPTY { // true also if offboard / invalid
return nil
}
touched := make(map[string]bool)
return self.stones_recurse(p, colour, touched, nil)
}
func (self *Board) stones_recurse(p string, colour Colour, touched map[string]bool, ret []string) []string {
// Note the constant returning and updating of ret since appends are not visible to caller otherwise.
touched[p] = true
ret = append(ret, p)
for _, a := range AdjacentPoints(p, self.Size) {
if self.get_fast(a) == colour {
if touched[a] == false {
ret = self.stones_recurse(a, colour, touched, ret)
}
}
}
return ret
}
// HasLiberties checks whether the group at point p has any liberties. The
// argument should be an SGF coordinate, e.g. "dd". For groups of stones on
// normal boards, this is always true, but can be false if the calling program
// is manipulating the board directly.
//
// If the point p is empty, returns false.
func (self *Board) HasLiberties(p string) bool {
colour := self.Get(p)
if colour == EMPTY { // true also if offboard / invalid
return false
}
touched := make(map[string]bool)
return self.has_liberties_recurse(p, colour, touched)
}
func (self *Board) has_liberties_recurse(p string, colour Colour, touched map[string]bool) bool {
touched[p] = true
for _, a := range AdjacentPoints(p, self.Size) {
a_colour := self.get_fast(a)
if a_colour == EMPTY {
return true
} else if a_colour == colour {
if touched[a] == false {
if self.has_liberties_recurse(a, colour, touched) {
return true
}
}
}
}
return false
}
// Liberties returns the liberties of the group at point p, in arbitrary order.
// The argument should be an SGF coordinate, e.g. "dd".
func (self *Board) Liberties(p string) []string {
colour := self.Get(p)
if colour == EMPTY { // true also if offboard / invalid
return nil
}
touched := make(map[string]bool)
touched[p] = true // Note this - slightly different setup than the other functions
return self.liberties_recurse(p, colour, touched, nil)
}
func (self *Board) liberties_recurse(p string, colour Colour, touched map[string]bool, ret []string) []string {
// Note that this function uses the touched map in a different way from others.
// Note the constant returning and updating of ret since appends are not visible to caller otherwise.
for _, a := range AdjacentPoints(p, self.Size) {
t := touched[a]
if t == false {
touched[a] = true
a_colour := self.get_fast(a)
if a_colour == EMPTY {
ret = append(ret, a)
} else if a_colour == colour {
ret = self.liberties_recurse(a, colour, touched, ret)
}
}
}
return ret
}
// Singleton returns true if the specified stone is a group of size 1. The
// argument should be an SGF coordinate, e.g. "dd".
func (self *Board) Singleton(p string) bool {
colour := self.Get(p)
if colour == EMPTY { // true also if offboard / invalid
return false
}
for _, a := range AdjacentPoints(p, self.Size) {
if self.get_fast(a) == colour {
return false
}
}
return true
}
// Legal returns true if a play at point p would be legal. The argument should
// be an SGF coordinate, e.g. "dd". The colour is determined intelligently. The
// board is not changed. If false, the reason is given in the error.
func (self *Board) Legal(p string) (bool, error) {
return self.LegalColour(p, self.Player)
}
// LegalColour is like Legal, except the colour is specified rather than being
// automatically determined.
func (self *Board) LegalColour(p string, colour Colour) (bool, error) {
if colour != BLACK && colour != WHITE {
return false, fmt.Errorf("colour not BLACK or WHITE")
}
x, y, onboard := ParsePoint(p, self.Size)
if onboard == false {
return false, fmt.Errorf("invalid or off-board string %q", p)
}
if self.State[x][y] != EMPTY {
return false, fmt.Errorf("point %q (%v,%v) was not empty", p, x, y)
}
if self.Ko == p {
if colour == self.Player { // i.e. we've not forced a move by the wrong colour.
return false, fmt.Errorf("ko recapture at %q (%v,%v) forbidden", p, x, y)
}
}
has_own_liberties := false
for _, a := range AdjacentPoints(p, self.Size) {
if self.get_fast(a) == EMPTY {
has_own_liberties = true
break
}
}
if has_own_liberties == false {
// The move we are playing will have no liberties of its own.
// Therefore, it will be legal iff it has a neighbour which:
//
// - Is an enemy group with 1 liberty, or
// - Is a friendly group with 2 or more liberties.
allowed := false
for _, a := range AdjacentPoints(p, self.Size) {
if self.get_fast(a) == colour.Opposite() {
if len(self.Liberties(a)) == 1 {
allowed = true
break
}
} else if self.get_fast(a) == colour {
if len(self.Liberties(a)) >= 2 {
allowed = true
break
}
} else {
panic("wat")
}
}
if allowed == false {
return false, fmt.Errorf("suicide at %q (%v,%v) forbidden", p, x, y)
}
}
// The move is legal!
return true, nil
}