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  }