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 }