...

Source file src/code.rocketnine.space/tslocum/tabula/board.go

Documentation: code.rocketnine.space/tslocum/tabula

     1  package tabula
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"math"
     7  	"sort"
     8  	"sync"
     9  )
    10  
    11  var (
    12  	AnalysisBufferSize    = 128
    13  	SubAnalysisBufferSize = 3072
    14  )
    15  
    16  const (
    17  	SpaceHomePlayer      int8 = 0
    18  	SpaceHomeOpponent    int8 = 25
    19  	SpaceBarPlayer       int8 = 26
    20  	SpaceBarOpponent     int8 = 27
    21  	SpaceRoll1           int8 = 28
    22  	SpaceRoll2           int8 = 29
    23  	SpaceRoll3           int8 = 30
    24  	SpaceRoll4           int8 = 31
    25  	SpaceEnteredPlayer   int8 = 32 // Whether the player has fully entered the board. Only used in acey-deucey games.
    26  	SpaceEnteredOpponent int8 = 33 // Whether the opponent has fully entered the board. Only used in acey-deucey games.
    27  	SpaceVariant         int8 = 34 // 0 - Backgammon, 1 - Acey-deucey, 2 - Tabula.
    28  )
    29  
    30  const (
    31  	boardSpaces = 35
    32  )
    33  
    34  const (
    35  	VariantBackgammon int8 = 0
    36  	VariantAceyDeucey int8 = 1
    37  	VariantTabula     int8 = 2
    38  )
    39  
    40  // Board represents the state of a game. It contains spaces for the checkers,
    41  // as well as four "spaces" which contain the available die rolls.
    42  type Board [boardSpaces]int8
    43  
    44  // NewBoard returns a new board with checkers placed in their starting positions.
    45  func NewBoard(variant int8) Board {
    46  	if variant != VariantBackgammon {
    47  		return Board{15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 0, 0, 0, 0, 1}
    48  	}
    49  	return Board{0, -2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0}
    50  }
    51  
    52  func (b Board) SetValue(space int, value int8) Board {
    53  	b[space] = value
    54  	return b
    55  }
    56  
    57  // Move moves a checker on the board.
    58  func (b Board) Move(from int8, to int8, player int8) Board {
    59  	if b[from] == 0 || (player == 1 && b[from] < 0) || (player == 2 && b[from] > 0) {
    60  		log.Panic("illegal move: no from checker", from, to, player)
    61  	} else if b[to] != 0 {
    62  		if (player == 1 && b[to] == -1) || (player == 2 && b[to] == 1) {
    63  			b[to] = 0
    64  			if player == 1 {
    65  				b[SpaceBarOpponent]--
    66  			} else {
    67  				b[SpaceBarPlayer]++
    68  			}
    69  		} else if (player == 1 && b[to] < 0) || (player == 2 && b[to] > 0) {
    70  			b.Print()
    71  			log.Panic("illegal move: existing checkers at to space", from, to, player, b[to])
    72  		}
    73  	}
    74  	delta := int8(1)
    75  	if player == 2 {
    76  		delta = -1
    77  	}
    78  	b[from], b[to] = b[from]-delta, b[to]+delta
    79  	if (player == 1 && from == SpaceHomePlayer && b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] == 0) || (player == 2 && from == SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] == 0) {
    80  		if player == 1 {
    81  			b[SpaceEnteredPlayer] = 1
    82  		} else {
    83  			b[SpaceEnteredOpponent] = 1
    84  		}
    85  	}
    86  	return b
    87  }
    88  
    89  // checkers returns the number of checkers that belong to the spcified player at the provided space.
    90  func checkers(player int8, v int8) int8 {
    91  	if player == 1 && v > 0 {
    92  		return v
    93  	} else if player == 2 && v < 0 {
    94  		return v * -1
    95  	}
    96  	return 0
    97  }
    98  
    99  func (b Board) MayBearOff(player int8) bool {
   100  	if b[SpaceVariant] != VariantBackgammon && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) {
   101  		return false
   102  	} else if b[SpaceVariant] == VariantTabula && !b.SecondHalf(player) {
   103  		return false
   104  	}
   105  	barSpace := SpaceBarPlayer
   106  	if player == 2 {
   107  		barSpace = SpaceBarOpponent
   108  	}
   109  	if checkers(player, b[barSpace]) != 0 {
   110  		return false
   111  	}
   112  	if b[SpaceVariant] != VariantTabula {
   113  		if player == 1 {
   114  			for space := 24; space > 6; space-- {
   115  				if checkers(player, b[space]) != 0 {
   116  					return false
   117  				}
   118  			}
   119  		} else {
   120  			for space := 1; space < 19; space++ {
   121  				if checkers(player, b[space]) != 0 {
   122  					return false
   123  				}
   124  			}
   125  		}
   126  	}
   127  	return true
   128  }
   129  
   130  func (b Board) spaceDiff(player int8, from int8, to int8) int8 {
   131  	switch {
   132  	case from < 0 || from > 27 || to < 0 || to > 27:
   133  		return 0
   134  	case to == SpaceBarPlayer || to == SpaceBarOpponent:
   135  		return 0
   136  	case (from == SpaceHomePlayer || from == SpaceHomeOpponent || from == SpaceBarPlayer || from == SpaceBarOpponent) && (to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomePlayer || to == SpaceHomeOpponent):
   137  		return 0
   138  	case to == SpaceHomePlayer:
   139  		if player == 2 {
   140  			return 0
   141  		}
   142  		if b[SpaceVariant] == VariantTabula {
   143  			if (player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0) || !b.SecondHalf(player) {
   144  				return 0
   145  			}
   146  			return 25 - from
   147  		}
   148  		return from
   149  	case to == SpaceHomeOpponent:
   150  		if player == 1 {
   151  			return 0
   152  		}
   153  		return 25 - from
   154  	case from == SpaceHomePlayer || from == SpaceHomeOpponent:
   155  		if (player == 1 && from == SpaceHomeOpponent) || (player == 2 && from == SpaceHomePlayer) {
   156  			return 0
   157  		}
   158  		switch b[SpaceVariant] {
   159  		case VariantAceyDeucey:
   160  			if player == 1 && from == SpaceHomePlayer && b[SpaceEnteredPlayer] == 0 {
   161  				return 25 - to
   162  			} else if player == 2 && from == SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0 {
   163  				return to
   164  			}
   165  		case VariantTabula:
   166  			if (player == 1 && from != SpaceHomePlayer && b[SpaceEnteredPlayer] == 0) || (player == 2 && from != SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0) {
   167  				return 0
   168  			}
   169  			return to
   170  		}
   171  		return 0
   172  	case from == SpaceBarPlayer:
   173  		if player == 2 {
   174  			return 0
   175  		}
   176  		if b[SpaceVariant] == VariantTabula {
   177  			return to
   178  		}
   179  		return 25 - to
   180  	case from == SpaceBarOpponent:
   181  		if player == 1 {
   182  			return 0
   183  		}
   184  		return to
   185  	default:
   186  		diff := to - from
   187  		if diff < 0 {
   188  			return diff * -1
   189  		}
   190  		return diff
   191  	}
   192  }
   193  
   194  // HaveRoll returns whether the player has a sufficient die roll for the specified move.
   195  func (b Board) HaveRoll(from int8, to int8, player int8) bool {
   196  	barSpace := SpaceBarPlayer
   197  	if player == 2 {
   198  		barSpace = SpaceBarOpponent
   199  	}
   200  	if b[barSpace] != 0 && from != barSpace {
   201  		return false
   202  	}
   203  
   204  	if b[SpaceVariant] == VariantTabula && to > 12 && to < 25 && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) {
   205  		return false
   206  	}
   207  
   208  	delta := b.spaceDiff(player, from, to)
   209  	if delta == 0 {
   210  		return false
   211  	}
   212  
   213  	if b[SpaceRoll1] == delta || b[SpaceRoll2] == delta || b[SpaceRoll3] == delta || b[SpaceRoll4] == delta {
   214  		return true
   215  	}
   216  
   217  	playerDelta := -1
   218  	playerHomeEnd := 6
   219  	if player == 2 {
   220  		playerDelta = 1
   221  		playerHomeEnd = 19
   222  	}
   223  	if b.MayBearOff(player) && b[SpaceVariant] == VariantBackgammon {
   224  		allowGreater := true
   225  		for checkSpace := 0; checkSpace < 6-int(delta); checkSpace++ {
   226  			if checkers(player, b[playerHomeEnd+checkSpace*playerDelta]) != 0 {
   227  				allowGreater = false
   228  				break
   229  			}
   230  		}
   231  		if allowGreater {
   232  			return (b[SpaceRoll1] >= delta || b[SpaceRoll2] >= delta || b[SpaceRoll3] >= delta || b[SpaceRoll4] >= delta)
   233  		}
   234  	}
   235  	return false
   236  }
   237  
   238  // UseRoll uses a die roll. UseRoll must be called before making a move.
   239  func (b Board) UseRoll(from int8, to int8, player int8) Board {
   240  	delta := b.spaceDiff(player, from, to)
   241  	if delta == 0 {
   242  		b.Print()
   243  		log.Panic("unknown space diff", from, to, player)
   244  	}
   245  
   246  	switch {
   247  	case b[SpaceRoll1] == delta:
   248  		b[SpaceRoll1] = 0
   249  		return b
   250  	case b[SpaceRoll2] == delta:
   251  		b[SpaceRoll2] = 0
   252  		return b
   253  	case b[SpaceRoll3] == delta:
   254  		b[SpaceRoll3] = 0
   255  		return b
   256  	case b[SpaceRoll4] == delta:
   257  		b[SpaceRoll4] = 0
   258  		return b
   259  	}
   260  
   261  	playerDelta := -1
   262  	playerHomeEnd := 6
   263  	if player == 2 {
   264  		playerDelta = 1
   265  		playerHomeEnd = 19
   266  	}
   267  	var allowGreater bool
   268  	if b.MayBearOff(player) && b[SpaceVariant] == VariantBackgammon {
   269  		allowGreater = true
   270  		for checkSpace := int8(0); checkSpace < 6-delta; checkSpace++ {
   271  			if checkers(player, b[playerHomeEnd+int(checkSpace)*playerDelta]) != 0 {
   272  				allowGreater = false
   273  				break
   274  			}
   275  		}
   276  	}
   277  	if !allowGreater {
   278  		b.Print()
   279  		log.Panic(fmt.Sprint(b), "no available roll for move", from, to, player, delta)
   280  	}
   281  
   282  	switch {
   283  	case b[SpaceRoll1] >= delta:
   284  		b[SpaceRoll1] = 0
   285  	case b[SpaceRoll2] >= delta:
   286  		b[SpaceRoll2] = 0
   287  	case b[SpaceRoll3] >= delta:
   288  		b[SpaceRoll3] = 0
   289  	case b[SpaceRoll4] >= delta:
   290  		b[SpaceRoll4] = 0
   291  	default:
   292  		b.Print()
   293  		log.Panic(fmt.Sprint(b), "no available roll for move", from, to, player, delta)
   294  	}
   295  	return b
   296  }
   297  
   298  func (b Board) _available(player int8) [][2]int8 {
   299  	homeSpace := SpaceHomePlayer
   300  	barSpace := SpaceBarPlayer
   301  	opponentBarSpace := SpaceBarOpponent
   302  	if player == 2 {
   303  		homeSpace = SpaceHomeOpponent
   304  		barSpace = SpaceBarOpponent
   305  		opponentBarSpace = SpaceBarPlayer
   306  	}
   307  	mayBearOff := b.MayBearOff(player)
   308  	onBar := b[barSpace] != 0
   309  
   310  	var moves [][2]int8
   311  
   312  	if b[SpaceVariant] != VariantBackgammon && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) && b[homeSpace] != 0 {
   313  		for space := int8(1); space < 25; space++ {
   314  			v := b[space]
   315  			if ((player == 1 && v >= -1) || (player == 2 && v <= 1)) && b.HaveRoll(homeSpace, space, player) {
   316  				moves = append(moves, [2]int8{homeSpace, space})
   317  			}
   318  		}
   319  	}
   320  	for from := int8(0); from < 28; from++ {
   321  		if from == SpaceHomePlayer || from == SpaceHomeOpponent || from == opponentBarSpace || checkers(player, b[from]) == 0 || (onBar && from != barSpace) {
   322  			continue
   323  		}
   324  		if player == 1 && b[SpaceVariant] != VariantTabula {
   325  			for to := int8(0); to < from; to++ {
   326  				if to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomeOpponent || (to == SpaceHomePlayer && !mayBearOff) {
   327  					continue
   328  				}
   329  				v := b[to]
   330  				if (player == 1 && v < -1) || (player == 2 && v > 1) || !b.HaveRoll(from, to, player) {
   331  					continue
   332  				}
   333  				moves = append(moves, [2]int8{from, to})
   334  			}
   335  		} else { // TODO clean up
   336  			start := from + 1
   337  			if from == SpaceBarPlayer || from == SpaceBarOpponent {
   338  				start = 1
   339  			}
   340  			for i := start; i <= 25; i++ {
   341  				to := i
   342  				if player == 1 && to == SpaceHomeOpponent {
   343  					to = SpaceHomePlayer
   344  				} else if player == 2 && to == SpaceHomePlayer {
   345  					to = SpaceHomeOpponent
   346  				}
   347  				if to == SpaceBarPlayer || to == SpaceBarOpponent || (((player == 1 && to == SpaceHomePlayer) || (player == 2 && to == SpaceHomeOpponent)) && !mayBearOff) {
   348  					continue
   349  				}
   350  				v := b[to]
   351  				if (player == 1 && v < -1) || (player == 2 && v > 1) || !b.HaveRoll(from, to, player) {
   352  					continue
   353  				}
   354  				moves = append(moves, [2]int8{from, to})
   355  			}
   356  		}
   357  	}
   358  
   359  	return moves
   360  }
   361  
   362  // Available returns legal moves available.
   363  func (b Board) Available(player int8) ([][4][2]int8, []Board) {
   364  	var allMoves [][4][2]int8
   365  
   366  	resultMutex := &sync.Mutex{}
   367  	movesFound := func(moves [4][2]int8) bool {
   368  		resultMutex.Lock()
   369  		for i := range allMoves {
   370  			if movesEqual(allMoves[i], moves) {
   371  				resultMutex.Unlock()
   372  				return true
   373  			}
   374  		}
   375  		resultMutex.Unlock()
   376  		return false
   377  	}
   378  
   379  	var boards []Board
   380  	a := b._available(player)
   381  	maxLen := 1
   382  	for _, move := range a {
   383  		newBoard := b.UseRoll(move[0], move[1], player).Move(move[0], move[1], player)
   384  		newAvailable := newBoard._available(player)
   385  		if len(newAvailable) == 0 {
   386  			moves := [4][2]int8{move}
   387  			if !movesFound(moves) {
   388  				allMoves = append(allMoves, moves)
   389  				boards = append(boards, newBoard)
   390  			}
   391  			continue
   392  		}
   393  		for _, move2 := range newAvailable {
   394  			newBoard2 := newBoard.UseRoll(move2[0], move2[1], player).Move(move2[0], move2[1], player)
   395  			newAvailable2 := newBoard2._available(player)
   396  			if len(newAvailable2) == 0 {
   397  				moves := [4][2]int8{move, move2}
   398  				if !movesFound(moves) {
   399  					allMoves = append(allMoves, moves)
   400  					boards = append(boards, newBoard2)
   401  					if maxLen <= 2 {
   402  						maxLen = 2
   403  					}
   404  				}
   405  				continue
   406  			}
   407  			for _, move3 := range newAvailable2 {
   408  				newBoard3 := newBoard2.UseRoll(move3[0], move3[1], player).Move(move3[0], move3[1], player)
   409  				newAvailable3 := newBoard3._available(player)
   410  				if len(newAvailable3) == 0 {
   411  					moves := [4][2]int8{move, move2, move3}
   412  					if !movesFound(moves) {
   413  						allMoves = append(allMoves, moves)
   414  						boards = append(boards, newBoard3)
   415  						if maxLen <= 2 {
   416  							maxLen = 3
   417  						}
   418  					}
   419  					continue
   420  				}
   421  				for _, move4 := range newAvailable3 {
   422  					newBoard4 := newBoard3.UseRoll(move4[0], move4[1], player).Move(move4[0], move4[1], player)
   423  					moves := [4][2]int8{move, move2, move3, move4}
   424  					if !movesFound(moves) {
   425  						allMoves = append(allMoves, moves)
   426  						boards = append(boards, newBoard4)
   427  						maxLen = 4
   428  					}
   429  				}
   430  			}
   431  		}
   432  	}
   433  	var newMoves [][4][2]int8
   434  	for i := 0; i < len(allMoves); i++ {
   435  		l := 0
   436  		if (allMoves[i][3][0] != 0 || allMoves[i][3][1] != 0) && allMoves[i][2][0] == 0 && allMoves[i][2][1] == 0 {
   437  			allMoves[i][2][0], allMoves[i][2][1] = allMoves[i][3][0], allMoves[i][3][1]
   438  			allMoves[i][2][0], allMoves[i][2][1] = 0, 0
   439  		}
   440  		if (allMoves[i][2][0] != 0 || allMoves[i][2][1] != 0) && allMoves[i][1][0] == 0 && allMoves[i][1][1] == 0 {
   441  			allMoves[i][1][0], allMoves[i][1][1] = allMoves[i][2][0], allMoves[i][2][1]
   442  			allMoves[i][2][0], allMoves[i][2][1] = 0, 0
   443  		}
   444  		if (allMoves[i][1][0] != 0 || allMoves[i][1][1] != 0) && allMoves[i][0][0] == 0 && allMoves[i][0][1] == 0 {
   445  			allMoves[i][0][0], allMoves[i][0][1] = allMoves[i][1][0], allMoves[i][1][1]
   446  			allMoves[i][1][0], allMoves[i][1][1] = 0, 0
   447  		}
   448  		for j := 0; j < 4; j++ {
   449  			if allMoves[i][j][0] == 0 && allMoves[i][j][1] == 0 {
   450  				break
   451  			}
   452  			l = j + 1
   453  		}
   454  		if l >= maxLen {
   455  			newMoves = append(newMoves, allMoves[i])
   456  		}
   457  	}
   458  	return newMoves, boards
   459  }
   460  
   461  func (b Board) FirstLast(player int8) (playerFirst int8, opponentLast int8) {
   462  	playerFirst, opponentLast = -1, -1
   463  	if b[SpaceBarPlayer] != 0 || b[SpaceBarOpponent] != 0 || b[SpaceVariant] == VariantTabula {
   464  		return playerFirst, opponentLast
   465  	} else if b[SpaceVariant] == VariantAceyDeucey && ((b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] != 0) || (b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] != 0)) {
   466  		return playerFirst, opponentLast
   467  	}
   468  	for space := int8(1); space < 25; space++ {
   469  		v := b[space]
   470  		if v == 0 {
   471  			continue
   472  		} else if v > 0 {
   473  			if space > playerFirst {
   474  				playerFirst = space
   475  			}
   476  		} else {
   477  			if opponentLast == -1 {
   478  				opponentLast = space
   479  			}
   480  		}
   481  	}
   482  	if player == 2 {
   483  		return opponentLast, playerFirst
   484  	}
   485  	return playerFirst, opponentLast
   486  }
   487  
   488  func (b Board) Past() bool {
   489  	playerFirst, opponentLast := b.FirstLast(1)
   490  	if playerFirst == -1 || opponentLast == -1 {
   491  		return false
   492  	}
   493  	return playerFirst < opponentLast
   494  }
   495  
   496  func (b Board) SecondHalf(player int8) bool {
   497  	if b[SpaceVariant] != VariantTabula {
   498  		return false
   499  	}
   500  
   501  	switch player {
   502  	case 1:
   503  		if b[SpaceBarPlayer] != 0 {
   504  			return false
   505  		} else if b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] != 0 {
   506  			return false
   507  		}
   508  	case 2:
   509  		if b[SpaceBarOpponent] != 0 {
   510  			return false
   511  		} else if b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] != 0 {
   512  			return false
   513  		}
   514  	default:
   515  		log.Panicf("unknown player: %d", player)
   516  	}
   517  
   518  	for space := 1; space < 13; space++ {
   519  		v := b[space]
   520  		if (player == 1 && v > 0) || (player == 2 && v < 0) {
   521  			return false
   522  		}
   523  	}
   524  
   525  	return true
   526  }
   527  
   528  func (b Board) Pips(player int8) int {
   529  	var pips int
   530  	if b[SpaceVariant] != VariantBackgammon {
   531  		if player == 1 && b[SpaceEnteredPlayer] == 0 {
   532  			pips += int(checkers(player, b[SpaceHomePlayer])) * PseudoPips(player, SpaceHomePlayer, b[SpaceVariant])
   533  		} else if player == 2 && b[SpaceEnteredOpponent] == 0 {
   534  			pips += int(checkers(player, b[SpaceHomeOpponent])) * PseudoPips(player, SpaceHomeOpponent, b[SpaceVariant])
   535  		}
   536  	}
   537  	if player == 1 {
   538  		pips += int(checkers(player, b[SpaceBarPlayer])) * PseudoPips(player, SpaceBarPlayer, b[SpaceVariant])
   539  	} else {
   540  		pips += int(checkers(player, b[SpaceBarOpponent])) * PseudoPips(player, SpaceBarOpponent, b[SpaceVariant])
   541  	}
   542  	for space := int8(1); space < 25; space++ {
   543  		pips += int(checkers(player, b[space])) * PseudoPips(player, space, b[SpaceVariant])
   544  	}
   545  	return pips
   546  }
   547  
   548  func (b Board) Blots(player int8) int {
   549  	_, last := b.FirstLast(player)
   550  	o := opponent(player)
   551  	var pips int
   552  	var pastBlots int
   553  	var div int
   554  	for space := int8(1); space < 25; space++ {
   555  		if checkers(player, b[space]) == 1 {
   556  			if last != -1 && ((player == 1 && space < last) || (player == 2 && space > last)) && pastBlots == 0 {
   557  				pastBlots++
   558  				div = 4
   559  			} else {
   560  				div = 1
   561  			}
   562  			v := PseudoPips(o, space, b[SpaceVariant]) / div
   563  			if v < 1 {
   564  				v = 1
   565  			}
   566  			pips += v
   567  		}
   568  	}
   569  	return pips
   570  }
   571  
   572  func (b Board) evaluate(player int8, hitScore int, a *Analysis) {
   573  	pips := b.Pips(player)
   574  	score := float64(pips)
   575  	var blots int
   576  	if !a.Past {
   577  		blots = b.Blots(player)
   578  		score += float64(blots)*WeightBlot + float64(hitScore)*WeightHit
   579  	}
   580  	a.Pips = pips
   581  	a.Blots = blots
   582  	a.Hits = hitScore
   583  	a.PlayerScore = score
   584  	a.hitScore = hitScore
   585  }
   586  
   587  func (b Board) Evaluation(player int8, hitScore int, moves [4][2]int8) *Analysis {
   588  	a := &Analysis{
   589  		Board:  b,
   590  		Moves:  moves,
   591  		Past:   b.Past(),
   592  		player: player,
   593  		chance: 1,
   594  	}
   595  	b.evaluate(player, hitScore, a)
   596  	return a
   597  }
   598  
   599  func (b Board) Analyze(available [][4][2]int8, result *[]*Analysis) {
   600  	if len(available) == 0 {
   601  		*result = (*result)[:0]
   602  		return
   603  	}
   604  	const priorityScore = -1000000
   605  
   606  	var reuse []*[]*Analysis
   607  	for _, r := range *result {
   608  		if r.result != nil {
   609  			reuse = append(reuse, r.result)
   610  		}
   611  	}
   612  	*result = (*result)[:0]
   613  	reuseLen := len(reuse)
   614  	var reuseIndex int
   615  
   616  	w := &sync.WaitGroup{}
   617  
   618  	past := b.Past()
   619  	w.Add(len(available))
   620  	for _, moves := range available {
   621  		var r *[]*Analysis
   622  		if reuseIndex < reuseLen {
   623  			r = reuse[reuseIndex]
   624  			*r = (*r)[:0]
   625  			reuseIndex++
   626  		} else {
   627  			v := make([]*Analysis, 0, SubAnalysisBufferSize)
   628  			r = &v
   629  		}
   630  		a := &Analysis{
   631  			Board:       b,
   632  			Moves:       moves,
   633  			Past:        past,
   634  			player:      1,
   635  			chance:      1,
   636  			result:      r,
   637  			resultMutex: &sync.Mutex{},
   638  			wg:          w,
   639  		}
   640  		*result = append(*result, a)
   641  		analysisQueue <- a
   642  	}
   643  	w.Wait()
   644  
   645  	for _, a := range *result {
   646  		if a.player == 1 && !a.Past {
   647  			var oppPips float64
   648  			var oppBlots float64
   649  			var oppHits float64
   650  			var oppScore float64
   651  			var count float64
   652  			for _, r := range *a.result {
   653  				oppPips += float64(r.Pips)
   654  				oppBlots += float64(r.Blots)
   655  				oppHits += float64(r.Hits)
   656  				oppScore += r.PlayerScore
   657  				count++
   658  			}
   659  			if count == 0 {
   660  				a.Score = a.PlayerScore
   661  			} else {
   662  				a.OppPips = (oppPips / count)
   663  				a.OppBlots = (oppBlots / count)
   664  				a.OppHits = (oppHits / count)
   665  				a.OppScore = (oppScore / count)
   666  				score := a.PlayerScore
   667  				if !math.IsNaN(oppScore) {
   668  					score += a.OppScore * WeightOppScore
   669  				}
   670  				a.Score = score
   671  			}
   672  		} else {
   673  			a.Score = a.PlayerScore
   674  		}
   675  		if a.player == 1 && !past && a.Past {
   676  			a.Score += priorityScore
   677  		}
   678  	}
   679  
   680  	if b[SpaceVariant] != VariantTabula && b.StartingPosition(1) {
   681  		r1, r2 := b[SpaceRoll1], b[SpaceRoll2]
   682  		if r2 > r1 {
   683  			r1, r2 = r2, r1
   684  		}
   685  		var opening [4][2]int8
   686  		if r1 == r2 {
   687  			switch r1 {
   688  			case 1:
   689  				opening = [4][2]int8{{24, 23}, {24, 23}, {6, 5}, {6, 5}}
   690  			case 2:
   691  				opening = [4][2]int8{{13, 11}, {13, 11}, {11, 9}, {11, 9}}
   692  			case 3:
   693  				opening = [4][2]int8{{13, 10}, {13, 10}, {10, 7}, {10, 7}}
   694  			case 4:
   695  				opening = [4][2]int8{{13, 9}, {13, 9}, {6, 2}, {6, 2}}
   696  			case 5:
   697  				opening = [4][2]int8{{13, 8}, {13, 8}, {8, 3}, {8, 3}}
   698  			case 6:
   699  				opening = [4][2]int8{{24, 18}, {24, 18}, {13, 7}, {13, 7}}
   700  			}
   701  		} else {
   702  			switch r1 {
   703  			case 2:
   704  				opening = [4][2]int8{{13, 11}, {6, 5}}
   705  			case 3:
   706  				switch r2 {
   707  				case 1:
   708  					opening = [4][2]int8{{8, 5}, {6, 5}}
   709  				case 2:
   710  					opening = [4][2]int8{{13, 11}, {13, 10}}
   711  				}
   712  			case 4:
   713  				switch r2 {
   714  				case 1:
   715  					opening = [4][2]int8{{24, 23}, {13, 9}}
   716  				case 2:
   717  					opening = [4][2]int8{{8, 4}, {6, 4}}
   718  				case 3:
   719  					opening = [4][2]int8{{13, 10}, {13, 9}}
   720  				}
   721  			case 5:
   722  				switch r2 {
   723  				case 1:
   724  					opening = [4][2]int8{{24, 23}, {13, 8}}
   725  				case 2:
   726  					opening = [4][2]int8{{24, 22}, {13, 8}}
   727  				case 3:
   728  					opening = [4][2]int8{{8, 3}, {6, 3}}
   729  				case 4:
   730  					opening = [4][2]int8{{24, 20}, {13, 8}}
   731  				}
   732  			case 6:
   733  				switch r2 {
   734  				case 1:
   735  					opening = [4][2]int8{{13, 7}, {8, 7}}
   736  				case 2:
   737  					opening = [4][2]int8{{24, 18}, {13, 11}}
   738  				case 3:
   739  					opening = [4][2]int8{{24, 18}, {13, 10}}
   740  				case 4:
   741  					opening = [4][2]int8{{8, 2}, {6, 2}}
   742  				case 5:
   743  					opening = [4][2]int8{{24, 18}, {18, 13}}
   744  				}
   745  			}
   746  		}
   747  		for _, a := range *result {
   748  			if movesEqual(a.Moves, opening) {
   749  				a.Score = priorityScore
   750  				break
   751  			}
   752  		}
   753  	}
   754  
   755  	sort.Slice(*result, func(i, j int) bool {
   756  		return (*result)[i].Score < (*result)[j].Score
   757  	})
   758  }
   759  
   760  func (b Board) StartingPosition(player int8) bool {
   761  	if player == 1 {
   762  		return b[6] == 5 && b[8] == 3 && b[13] == 5 && b[24] == 2
   763  	}
   764  	return b[1] == -2 && b[12] == -5 && b[17] == -3 && b[19] == -5
   765  }
   766  
   767  func (b Board) ChooseDoubles(result *[]*Analysis) int {
   768  	if b[SpaceVariant] != VariantAceyDeucey {
   769  		return 0
   770  	}
   771  
   772  	bestDoubles := 6
   773  	bestScore := math.MaxFloat64
   774  
   775  	var available [][4][2]int8
   776  	for i := 0; i < 6; i++ {
   777  		doubles := int8(i + 1)
   778  		bc := b
   779  		bc[SpaceRoll1], bc[SpaceRoll2], bc[SpaceRoll3], bc[SpaceRoll4] = doubles, doubles, doubles, doubles
   780  
   781  		available, _ = bc.Available(1)
   782  		bc.Analyze(available, result)
   783  		if len(*result) > 0 && (*result)[0].Score < bestScore {
   784  			bestDoubles = i + 1
   785  			bestScore = (*result)[0].Score
   786  		}
   787  	}
   788  
   789  	return bestDoubles
   790  }
   791  
   792  func (b Board) Print() {
   793  	log.Printf("%+v", b)
   794  }
   795  
   796  func opponent(player int8) int8 {
   797  	if player == 1 {
   798  		return 2
   799  	}
   800  	return 1
   801  }
   802  
   803  func spaceValue(player int8, space int8, variant int8) int {
   804  	if space == SpaceHomePlayer || space == SpaceHomeOpponent || space == SpaceBarPlayer || space == SpaceBarOpponent {
   805  		return 25
   806  	} else if player == 1 || variant == VariantTabula {
   807  		return int(space)
   808  	} else {
   809  		return int(25 - space)
   810  	}
   811  }
   812  
   813  func PseudoPips(player int8, space int8, variant int8) int {
   814  	v := 6 + spaceValue(player, space, variant) + int(math.Exp(float64(spaceValue(player, space, variant))*0.2))*2
   815  	if space == SpaceHomePlayer || space == SpaceHomeOpponent || (variant == VariantTabula && space < 13) || (variant != VariantTabula && ((player == 1 && (space > 6 || space == SpaceBarPlayer)) || (player == 2 && (space < 19 || space == SpaceBarOpponent)))) {
   816  		v += 24
   817  	}
   818  	return v
   819  }
   820  
   821  func movesEqual(a [4][2]int8, b [4][2]int8) bool {
   822  	if a[0][0] == b[0][0] && a[0][1] == b[0][1] { // 1
   823  		if a[1][0] == b[1][0] && a[1][1] == b[1][1] { // 2
   824  			if (a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) || // 3,4
   825  				(a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) { // 4,3
   826  				return true
   827  			}
   828  		}
   829  		if a[1][0] == b[2][0] && a[1][1] == b[2][1] { // 3
   830  			if (a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) || // 2,4
   831  				(a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) { // 4,2
   832  				return true
   833  			}
   834  		}
   835  		if a[1][0] == b[3][0] && a[1][1] == b[3][1] { // 4
   836  			if (a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) || // 3,2
   837  				(a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) { // 2,3
   838  				return true
   839  			}
   840  		}
   841  	}
   842  	if a[0][0] == b[1][0] && a[0][1] == b[1][1] { // 2
   843  		if a[1][0] == b[0][0] && a[1][1] == b[0][1] { // 1
   844  			if (a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) || // 3,4
   845  				(a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) { // 4,3
   846  				return true
   847  			}
   848  		}
   849  		if a[1][0] == b[2][0] && a[1][1] == b[2][1] { // 3
   850  			if (a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) || // 4,1
   851  				(a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) { // 1,4
   852  				return true
   853  			}
   854  		}
   855  		if a[1][0] == b[3][0] && a[1][1] == b[3][1] { // 4
   856  			if (a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) || // 3,1
   857  				(a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) { // 1,3
   858  				return true
   859  			}
   860  		}
   861  	}
   862  	if a[0][0] == b[2][0] && a[0][1] == b[2][1] { // 3
   863  		if a[1][0] == b[0][0] && a[1][1] == b[0][1] { // 1
   864  			if (a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) || // 2,4
   865  				(a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) { // 4,2
   866  				return true
   867  			}
   868  		}
   869  		if a[1][0] == b[1][0] && a[1][1] == b[1][1] { // 2
   870  			if (a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[3][0] && a[3][1] == b[3][1]) || // 1,4
   871  				(a[2][0] == b[3][0] && a[2][1] == b[3][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) { // 4,1
   872  				return true
   873  			}
   874  		}
   875  		if a[1][0] == b[3][0] && a[1][1] == b[3][1] { // 4
   876  			if (a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) || // 2,1
   877  				(a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) { // 1,2
   878  				return true
   879  			}
   880  		}
   881  	}
   882  	if a[0][0] == b[3][0] && a[0][1] == b[3][1] { // 4
   883  		if a[1][0] == b[0][0] && a[1][1] == b[0][1] { // 1
   884  			if (a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) || // 3,2
   885  				(a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) { // 2,3
   886  				return true
   887  			}
   888  		}
   889  		if a[1][0] == b[1][0] && a[1][1] == b[1][1] { // 2
   890  			if (a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[2][0] && a[3][1] == b[2][1]) || // 1,3
   891  				(a[2][0] == b[2][0] && a[2][1] == b[2][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) { // 3,1
   892  				return true
   893  			}
   894  		}
   895  		if a[1][0] == b[2][0] && a[1][1] == b[2][1] { // 3
   896  			if (a[2][0] == b[0][0] && a[2][1] == b[0][1] && a[3][0] == b[1][0] && a[3][1] == b[1][1]) || // 1,2
   897  				(a[2][0] == b[1][0] && a[2][1] == b[1][1] && a[3][0] == b[0][0] && a[3][1] == b[0][1]) { // 2,1
   898  				return true
   899  			}
   900  		}
   901  	}
   902  	return false
   903  }
   904  

View as plain text