github.com/gorgonia/agogo@v0.1.1/game/komi/game.go (about) 1 package komi 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/gorgonia/agogo/game" 8 "github.com/pkg/errors" 9 ) 10 11 const ( 12 None = game.None 13 Black = game.Black 14 White = game.White 15 16 BlackP = game.Player(game.Black) 17 WhiteP = game.Player(game.White) 18 19 Pass = game.Single(-1) 20 ) 21 22 var _ game.State = &Game{} 23 var _ game.CoordConverter = &Game{} 24 25 type Game struct { 26 sync.Mutex 27 board []game.Colour 28 m, n int32 // board size is mxn, k surrounded to win 29 k float32 30 31 nextToMove game.Player 32 history []game.PlayerMove 33 historical [][]game.Colour 34 histPtr int 35 ws, bs float32 // white score and black score 36 z zobrist 37 38 // transient state 39 taken int 40 err error 41 } 42 43 func New(m, n, k int) *Game { 44 return &Game{ 45 m: int32(m), 46 n: int32(n), 47 k: float32(k), 48 board: make([]game.Colour, m*n), 49 nextToMove: BlackP, 50 z: makeZobrist(m, n), 51 } 52 } 53 54 func (g *Game) BoardSize() (int, int) { return int(g.m), int(g.n) } 55 56 func (g *Game) Board() []game.Colour { return g.board } 57 58 func (g *Game) Historical(i int) []game.Colour { return g.historical[i] } 59 60 func (g *Game) Hash() game.Zobrist { return game.Zobrist(g.z.hash) } 61 62 func (g *Game) ActionSpace() int { return int(g.m * g.n) } 63 64 func (g *Game) SetToMove(p game.Player) { g.Lock(); g.nextToMove = p; g.Unlock() } 65 66 func (g *Game) ToMove() game.Player { return g.nextToMove } 67 68 func (g *Game) LastMove() game.PlayerMove { 69 if len(g.history) > 0 { 70 return g.history[g.histPtr-1] 71 } 72 return game.PlayerMove{game.Player(game.None), Pass} 73 } 74 75 // Passes always returns -1. You can't pass in Komi 76 func (g *Game) Passes() int { return -1 } 77 78 func (g *Game) MoveNumber() int { return len(g.history) } 79 80 func (g *Game) Check(m game.PlayerMove) bool { 81 if m.Single.IsResignation() { 82 return true 83 } 84 85 // Pass not allowed! 86 if m.Single.IsPass() { 87 return false 88 } 89 90 if int(m.Single) >= len(g.board) { 91 return false 92 } 93 94 if g.board[int(m.Single)] != game.None { 95 return false 96 } 97 if _, err := g.check(m); err != nil { 98 // log.Printf("Checking %v. OK", m) 99 return false 100 } 101 return true 102 } 103 104 func (g *Game) Apply(m game.PlayerMove) game.State { 105 hb := make([]game.Colour, len(g.board)) 106 107 g.taken, g.err = g.apply(m) 108 if g.err != nil { 109 return g 110 } 111 g.Lock() 112 copy(hb, g.board) 113 g.histPtr++ 114 if len(g.history) < g.histPtr { 115 g.history = append(g.history, m) 116 } else { 117 g.history[g.histPtr-1] = m 118 } 119 g.historical = append(g.historical, hb) 120 g.nextToMove = Opponent(m.Player) 121 g.Unlock() 122 123 switch m.Player { 124 case BlackP: 125 g.bs += float32(g.taken) 126 case WhiteP: 127 g.ws += float32(g.taken) 128 } 129 return g 130 } 131 132 func (g *Game) Handicap() int { return 0 } 133 134 func (g *Game) Score(p game.Player) float32 { 135 if p == WhiteP { 136 return g.ws 137 } else if p == BlackP { 138 return g.bs 139 } 140 panic("unreachable") 141 } 142 143 func (g *Game) AdditionalScore() float32 { return 0 } 144 145 func (g *Game) Ended() (ended bool, winner game.Player) { 146 if g.ws >= g.k { 147 return true, WhiteP 148 } 149 if g.bs >= g.k { 150 return true, BlackP 151 } 152 153 var potentials []game.Single 154 for i := 0; i < len(g.board); i++ { 155 if g.board[i] == None { 156 potentials = append(potentials, game.Single(i)) 157 } 158 } 159 160 var currHasMoveLeft, oppHasMoveLeft bool 161 for _, pot := range potentials { 162 pm := game.PlayerMove{g.nextToMove, game.Single(pot)} 163 if g.Check(pm) { 164 currHasMoveLeft = true 165 break 166 } 167 } 168 for _, pot := range potentials { 169 pm := game.PlayerMove{Opponent(g.nextToMove), game.Single(pot)} 170 if g.Check(pm) { 171 oppHasMoveLeft = true 172 break 173 } 174 } 175 176 if currHasMoveLeft && oppHasMoveLeft { 177 return false, game.Player(game.None) 178 } 179 if g.ws > g.bs { 180 return true, game.Player(game.White) 181 } 182 if g.bs > g.ws { 183 return true, game.Player(game.Black) 184 } 185 return true, game.Player(game.None) 186 187 } 188 189 func (g *Game) Reset() { 190 for i := range g.board { 191 g.board[i] = game.None 192 } 193 g.history = g.history[:0] 194 g.historical = g.historical[:0] 195 g.histPtr = 0 196 g.nextToMove = BlackP 197 g.ws = 0 198 g.bs = 0 199 g.z = makeZobrist(int(g.m), int(g.n)) 200 } 201 202 func (g *Game) UndoLastMove() { 203 if len(g.history) > 0 { 204 g.board[int(g.history[g.histPtr-1].Single)] = game.None 205 g.histPtr-- 206 } 207 } 208 209 func (g *Game) Fwd() { 210 if len(g.history) > 0 { 211 g.histPtr++ 212 } 213 } 214 215 func (g *Game) Eq(other game.State) bool { 216 ot, ok := other.(*Game) 217 if !ok { 218 return false 219 } 220 if g.nextToMove != ot.nextToMove || 221 len(g.board) != len(ot.board) || 222 len(g.history) != len(ot.history) && 223 (len(g.history) > 0 && len(ot.history) > 0 && len(g.history[:g.histPtr-1]) != len(ot.history[:ot.histPtr-1])) { 224 return false 225 } 226 for i := range g.board { 227 if g.board[i] != ot.board[i] { 228 return false 229 } 230 } 231 return true 232 } 233 234 func (g *Game) Clone() game.State { 235 retVal := &Game{ 236 m: g.m, 237 n: g.n, 238 k: g.k, 239 board: make([]game.Colour, len(g.board)), 240 } 241 g.Lock() 242 copy(retVal.board, g.board) 243 retVal.history = make([]game.PlayerMove, len(g.history), len(g.history)+4) 244 retVal.historical = make([][]game.Colour, len(g.historical), len(g.historical)+4) 245 copy(retVal.history, g.history) 246 copy(retVal.historical, g.historical) 247 retVal.nextToMove = g.nextToMove 248 retVal.histPtr = g.histPtr 249 retVal.z = g.z.clone() 250 g.Unlock() 251 return retVal 252 } 253 254 func (g *Game) Format(s fmt.State, c rune) { 255 it := game.MakeIterator(g.board, g.m, g.n) 256 defer game.ReturnIterator(g.m, g.n, it) 257 switch c { 258 case 's', 'v': 259 for _, row := range it { 260 fmt.Fprint(s, "⎢ ") 261 for _, col := range row { 262 fmt.Fprintf(s, "%s ", col) 263 } 264 fmt.Fprint(s, "⎥\n") 265 } 266 } 267 } 268 269 func (g *Game) Err() error { return g.err } 270 271 func (g *Game) Itol(c game.Single) game.Coord { 272 x := int16(int32(c) / int32(g.m)) 273 y := int16(int32(c) % int32(g.m)) 274 return game.Coord{x, y} 275 } 276 277 // Ltoi takes a coordinate and return a single 278 func (g *Game) Ltoi(c game.Coord) game.Single { return game.Single(int32(c.X)*g.m + int32(c.Y)) } 279 280 func (g *Game) apply(m game.PlayerMove) (int, error) { 281 if !isValid(m.Player) { 282 return 0, errors.WithMessage(moveError(m), "Impossible player") 283 } 284 if m.Single.IsPass() { 285 return 0, errors.WithMessage(moveError(m), "Cannot pass") 286 } 287 288 if int32(m.Single) >= g.m*g.m { // don't check for negative moves. the special moves are to be made at the Game level 289 return 0, errors.WithMessage(moveError(m), "Impossible move") 290 } 291 292 // if the board location is not empty, then clearly we can't apply 293 if g.board[m.Single] != game.None { 294 return 0, errors.WithMessage(moveError(m), "Application Failure - board location not empty.") 295 } 296 297 captures, err := g.check(m) 298 if err != nil { 299 return 0, errors.WithMessage(err, "Application Failure.") 300 } 301 302 // the move is valid. 303 // make the move then update zobrist hash 304 g.board[m.Single] = game.Colour(m.Player) 305 g.z.update(m) 306 307 // remove prisoners 308 for _, prisoner := range captures { 309 g.board[prisoner] = game.None 310 g.z.update(game.PlayerMove{Player: Opponent(m.Player), Single: prisoner}) // Xoring the original colour 311 } 312 return len(captures), nil 313 } 314 315 // check will find the captures (if any) if the move is valid. If the move is invalid, an error will be returned 316 func (g *Game) check(m game.PlayerMove) (captures []game.Single, err error) { 317 if m.Single.IsPass() { 318 return nil, errors.New("Cannot pass") 319 } 320 321 c := g.Itol(m.Single) 322 it := game.MakeIterator(g.board, g.m, g.n) 323 defer game.ReturnIterator(g.m, g.n, it) 324 325 adj := g.adjacentsCoord(c) 326 for _, a := range adj { 327 if !g.isCoordValid(a) { 328 continue 329 } 330 331 if it[a.X][a.Y] == game.Colour(Opponent(m.Player)) { 332 // find Opponent stones with no liberties 333 nolibs := g.nolib(it, a, c) 334 for _, nl := range nolibs { 335 captures = append(captures, g.Ltoi(nl)) 336 } 337 } 338 } 339 if len(captures) > 0 { 340 return 341 } 342 // check for suicide moves 343 suicides := g.nolib(it, c, game.Coord{-5, -5}) // purposefully incomparable 344 if len(suicides) > 0 { 345 return nil, errors.WithMessage(moveError(m), "Suicide is not a valid option.") 346 } 347 return 348 } 349 350 // c is the position of the stone, potential is where a potential stone could be placed 351 func (g *Game) nolib(it [][]game.Colour, c, potential game.Coord) (retVal []game.Coord) { 352 found := true 353 founds := []game.Coord{c} 354 for found { 355 found = false 356 var group []game.Coord 357 for _, f := range founds { 358 adj := g.adjacentsCoord(f) 359 360 for _, a := range adj { 361 if !g.isCoordValid(a) { 362 continue 363 } 364 // does f have a free liberty 365 if it[a.X][a.Y] == game.None && !a.Eq(potential) { 366 return nil 367 } 368 // if the found node is not the same colour as its adjacent 369 if it[f.X][f.Y] != it[a.X][a.Y] { 370 continue 371 } 372 373 // check if we have a group 374 potentialGroup := true 375 for _, gr := range group { 376 if gr.Eq(a) { 377 potentialGroup = false 378 break 379 } 380 } 381 382 if potentialGroup { 383 for _, l := range retVal { 384 if l.Eq(a) { 385 potentialGroup = false 386 break 387 } 388 } 389 } 390 391 if potentialGroup { 392 group = append(group, a) 393 found = true 394 } 395 396 } 397 } 398 retVal = append(retVal, founds...) 399 founds = group 400 } 401 return retVal 402 } 403 404 // adjacentsCoord returns the adjacent positions given a coord 405 func (g *Game) adjacentsCoord(c game.Coord) (retVal [4]game.Coord) { 406 for i := range retVal { 407 retVal[i] = c.Add(adjacents[i]) 408 } 409 return retVal 410 } 411 412 func (g *Game) isCoordValid(c game.Coord) bool { 413 x, y := int32(c.X), int32(c.Y) 414 // check if valid 415 if x >= g.m || x < 0 { 416 return false 417 } 418 419 if y >= g.n || y < 0 { 420 return false 421 } 422 return true 423 } 424 425 var adjacents = [4]game.Coord{ 426 {0, 1}, 427 {1, 0}, 428 {0, -1}, 429 {-1, 0}, 430 } 431 432 // Opponent returns the colour of the Opponent player 433 func Opponent(p game.Player) game.Player { 434 switch game.Colour(p) { 435 case game.White: 436 return game.Player(game.Black) 437 case game.Black: 438 return game.Player(game.White) 439 } 440 panic("Unreachaable") 441 } 442 443 // isValid checks that a player is indeed valid 444 func isValid(p game.Player) bool { return game.Colour(p) == game.Black || game.Colour(p) == game.White }