github.com/gorgonia/agogo@v0.1.1/game/wq/wq.go (about) 1 // package 围碁 implements Go (the board game) related code 2 // 3 // 围碁 is a bastardized word. 4 // The first character is read "wei" in Chinese. The second is read "qi" in Chinese. 5 // However, the charcter 碁 is no longer actively used in Chinese. 6 // It is however, actively used in Japanese. Specifically, it's read "go" in Japanese. 7 // 8 // The main reason why this package is named with unicode characters instead of `package go` 9 // is because the standard library of the Go language have the prefix "go" 10 package 围碁 11 12 import ( 13 "fmt" 14 15 "github.com/gorgonia/agogo/game" 16 "github.com/pkg/errors" 17 ) 18 19 const ( 20 // for fast calculation of neighbours 21 shift = 4 22 mask = (1 << shift) - 1 23 ) 24 25 const ( 26 None = game.None 27 Black = game.Black 28 White = game.White 29 30 BlackP = game.Player(game.Black) 31 WhiteP = game.Player(game.White) 32 ) 33 34 // Opponent returns the colour of the opponent player 35 func Opponent(p game.Player) game.Player { 36 switch game.Colour(p) { 37 case game.White: 38 return game.Player(game.Black) 39 case game.Black: 40 return game.Player(game.White) 41 } 42 panic("Unreachaable") 43 } 44 45 // IsValid checks that a player is indeed valid 46 func IsValid(p game.Player) bool { return game.Colour(p) == game.Black || game.Colour(p) == game.White } 47 48 // Board represetns a board. 49 // 50 // The board was originally implemented with a *tensor.Dense. However it turns out that 51 // if many iterations of the board were needed, we would quite quickly run out of memory 52 // because the *tensor.Dense data structure is quite huge. 53 // 54 // Given we know the stride of the board, a decision was made to cull the fat from the data structure. 55 type Board struct { 56 size int32 57 data []game.Colour // backing data 58 it [][]game.Colour // iterator for quick access 59 zobrist // hashing of the board 60 } 61 62 func newBoard(size int) *Board { 63 data, it := makeBoard(size) 64 z := makeZobrist(size) 65 return &Board{ 66 size: int32(size), 67 data: data, 68 it: it, 69 zobrist: z, 70 } 71 } 72 73 // Clone clones the board 74 func (b *Board) Clone() *Board { 75 data, it := makeBoard(int(b.size)) 76 z := makeZobrist(int(b.size)) 77 z.hash = b.hash 78 copy(data, b.data) 79 copy(z.table, b.table) 80 return &Board{ 81 size: b.size, 82 data: data, 83 it: it, 84 zobrist: z, 85 } 86 } 87 88 // Eq checks that both are equal 89 func (b *Board) Eq(other *Board) bool { 90 if b == other { 91 return true 92 } 93 // easy to check stuff 94 if b.size != other.size || 95 b.hash != other.hash || 96 len(b.data) != len(other.data) || 97 len(b.zobrist.table) != len(other.zobrist.table) { 98 return false 99 } 100 101 for i, c := range b.data { 102 if c != other.data[i] { 103 return false 104 } 105 } 106 107 for i, r := range b.zobrist.table { 108 if r != other.zobrist.table[i] { 109 return false 110 } 111 } 112 return true 113 } 114 115 // Format implements fmt.Formatter 116 func (b *Board) Format(s fmt.State, c rune) { 117 switch c { 118 case 's': 119 for _, row := range b.it { 120 fmt.Fprint(s, "⎢ ") 121 for _, col := range row { 122 fmt.Fprintf(s, "%s ", col) 123 } 124 fmt.Fprint(s, "⎥\n") 125 } 126 } 127 } 128 129 // Reset resets the board state 130 func (b *Board) Reset() { 131 for i := range b.data { 132 b.data[i] = game.None 133 } 134 b.zobrist.hash = 0 135 } 136 137 // Hash returns the calculated hash of the board 138 func (b *Board) Hash() int32 { return b.hash } 139 140 // Apply returns the number of captures or an error, if a move were to be applied 141 func (b *Board) Apply(m game.PlayerMove) (byte, error) { 142 if !IsValid(m.Player) { 143 return 0, errors.WithMessage(moveError(m), "Impossible player") 144 } 145 146 if int32(m.Single) >= b.size*b.size { // don't check for negative moves. the special moves are to be made at the Game level 147 return 0, errors.WithMessage(moveError(m), "Impossible move") 148 } 149 150 // if the board location is not empty, then clearly we can't apply 151 if b.data[m.Single] != game.None { 152 return 0, errors.WithMessage(moveError(m), "Application Failure - board location not empty.") 153 } 154 155 captures, err := b.check(m) 156 if err != nil { 157 return 0, errors.WithMessage(err, "Application Failure.") 158 } 159 160 // the move is valid. 161 // make the move then update zobrist hash 162 b.data[m.Single] = game.Colour(m.Player) 163 b.zobrist.update(m) 164 165 // remove prisoners 166 for _, prisoner := range captures { 167 b.data[prisoner] = game.None 168 b.zobrist.update(game.PlayerMove{Player: Opponent(m.Player), Single: prisoner}) // Xoring the original colour 169 } 170 return byte(len(captures)), nil 171 } 172 173 func (b *Board) Score(player game.Player) float32 { 174 colour := game.Colour(player) 175 bd := make([]bool, len(b.data)) 176 q := make(chan int32, b.size*b.size) 177 adjacents := [4]int32{-b.size, 1, b.size, 1} 178 179 var reachable float32 180 for i := int32(0); i < int32(len(b.data)); i++ { 181 if b.data[i] == colour { 182 reachable++ 183 bd[i] = true 184 q <- i 185 } 186 } 187 for len(q) > 0 { 188 i := <-q 189 for _, adj := range adjacents { 190 a := i + adj 191 if a >= b.size || a < 0 { 192 continue 193 } 194 if !bd[a] && b.data[a] == None { 195 reachable++ 196 bd[a] = true 197 q <- a 198 } 199 } 200 } 201 return reachable 202 } 203 204 // check will find the captures (if any) if the move is valid. If the move is invalid, an error will be returned 205 func (b *Board) check(m game.PlayerMove) (captures []game.Single, err error) { 206 x := int16(int32(m.Single) / b.size) 207 y := int16(int32(m.Single) % b.size) 208 c := game.Coord{x, y} 209 210 adj := b.adjacentsCoord(c) 211 for _, a := range adj { 212 if !b.isCoordValid(a) { 213 continue 214 } 215 216 if b.it[a.X][a.Y] == game.Colour(Opponent(m.Player)) { 217 // find opponent stones with no liberties 218 nolibs := b.nolib(a, c) 219 for _, nl := range nolibs { 220 captures = append(captures, b.ltoi(nl)) 221 } 222 } 223 } 224 if len(captures) > 0 { 225 return 226 } 227 228 // check for suicide moves 229 suicides := b.nolib(c, game.Coord{-5, -5}) // purposefully incomparable 230 if len(suicides) > 0 { 231 return nil, errors.WithMessage(moveError(m), "Suicide is not a valid option.") 232 } 233 return 234 } 235 236 // c is the position of the stone, potential is where a potential stone could be placed 237 func (b *Board) nolib(c, potential game.Coord) (retVal []game.Coord) { 238 found := true 239 founds := []game.Coord{c} 240 for found { 241 found = false 242 var group []game.Coord 243 244 for _, f := range founds { 245 adj := b.adjacentsCoord(f) 246 247 for _, a := range adj { 248 if !b.isCoordValid(a) { 249 continue 250 } 251 // does f have a free liberty 252 if b.it[a.X][a.Y] == game.None && !a.Eq(potential) { 253 return nil 254 } 255 256 // if the found node is not the same colour as its adjacent 257 if b.it[f.X][f.Y] != b.it[a.X][a.Y] { 258 continue 259 } 260 261 // check if we have a group 262 potentialGroup := true 263 for _, g := range group { 264 if g.Eq(a) { 265 potentialGroup = false 266 break 267 } 268 } 269 270 if potentialGroup { 271 for _, l := range retVal { 272 if l.Eq(a) { 273 potentialGroup = false 274 break 275 } 276 } 277 } 278 279 if potentialGroup { 280 group = append(group, a) 281 found = true 282 } 283 284 } 285 } 286 retVal = append(retVal, founds...) 287 founds = group 288 } 289 return retVal 290 } 291 292 // ltoi takes a coordinate and return a single 293 func (b *Board) ltoi(c game.Coord) game.Single { return game.Single(int32(c.X)*b.size + int32(c.Y)) } 294 295 // adjacentsCoord returns the adjacent positions given a coord 296 func (b *Board) adjacentsCoord(c game.Coord) (retVal [4]game.Coord) { 297 for i := range retVal { 298 retVal[i] = c.Add(adjacents[i]) 299 } 300 return retVal 301 } 302 303 func (b *Board) isCoordValid(c game.Coord) bool { 304 x, y := int32(c.X), int32(c.Y) 305 // check if valid 306 if x >= b.size || x < 0 { 307 return false 308 } 309 310 if y >= b.size || y < 0 { 311 return false 312 } 313 return true 314 } 315 316 var adjacents = [4]game.Coord{ 317 {0, 1}, 318 {1, 0}, 319 {0, -1}, 320 {-1, 0}, 321 }