tabula/board.go

1009 lines
27 KiB
Go
Raw Normal View History

2023-11-30 07:59:50 +00:00
package tabula
import (
"fmt"
2023-11-30 07:59:50 +00:00
"log"
2023-12-02 18:29:27 +00:00
"math"
2023-11-30 07:59:50 +00:00
"sort"
2023-11-30 09:10:31 +00:00
"sync"
2023-11-30 07:59:50 +00:00
)
2023-12-11 20:15:26 +00:00
var (
AnalysisBufferSize = 128
2023-12-12 19:08:31 +00:00
SubAnalysisBufferSize = 3072
2023-12-06 23:58:31 +00:00
)
2023-11-30 07:59:50 +00:00
const (
SpaceHomePlayer int8 = 0
SpaceHomeOpponent int8 = 25
SpaceBarPlayer int8 = 26
SpaceBarOpponent int8 = 27
SpaceRoll1 int8 = 28
SpaceRoll2 int8 = 29
SpaceRoll3 int8 = 30
SpaceRoll4 int8 = 31
SpaceEnteredPlayer int8 = 32 // Whether the player has fully entered the board. Only used in acey-deucey games.
SpaceEnteredOpponent int8 = 33 // Whether the opponent has fully entered the board. Only used in acey-deucey games.
2024-01-07 21:26:03 +00:00
SpaceVariant int8 = 34 // 0 - Backgammon, 1 - Acey-deucey, 2 - Tabula.
2023-11-30 07:59:50 +00:00
)
const (
2023-12-11 01:22:40 +00:00
boardSpaces = 35
2023-11-30 07:59:50 +00:00
)
2024-01-07 21:26:03 +00:00
const (
VariantBackgammon int8 = 0
VariantAceyDeucey int8 = 1
VariantTabula int8 = 2
)
2023-11-30 07:59:50 +00:00
// Board represents the state of a game. It contains spaces for the checkers,
// as well as four "spaces" which contain the available die rolls.
type Board [boardSpaces]int8
// NewBoard returns a new board with checkers placed in their starting positions.
2024-01-07 21:26:03 +00:00
func NewBoard(variant int8) Board {
if variant != VariantBackgammon {
2023-12-11 01:22:40 +00:00
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}
}
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}
}
func (b Board) String() string {
var board []byte
for i, v := range b {
if i != 0 {
board = append(board, ',')
}
board = append(board, []byte(fmt.Sprintf("%d", v))...)
}
variant := "Backgammon"
switch b[SpaceVariant] {
case VariantAceyDeucey:
variant = "Acey-deucey"
case VariantTabula:
variant = "Tabula"
}
entered1, entered2 := "Y", "Y"
if b[SpaceEnteredPlayer] == 0 {
entered1 = "N"
}
if b[SpaceEnteredOpponent] == 0 {
entered2 = "N"
}
off1 := b[SpaceHomePlayer]
off2 := b[SpaceHomeOpponent] * -1
var rolls []byte
if b[SpaceRoll1] != 0 {
rolls = append(rolls, []byte(fmt.Sprintf("%d", b[SpaceRoll1]))...)
}
if b[SpaceRoll2] != 0 {
if len(rolls) != 0 {
rolls = append(rolls, ' ')
}
rolls = append(rolls, []byte(fmt.Sprintf("%d", b[SpaceRoll2]))...)
}
if b[SpaceRoll3] != 0 {
if len(rolls) != 0 {
rolls = append(rolls, ' ')
}
rolls = append(rolls, []byte(fmt.Sprintf("%d", b[SpaceRoll3]))...)
}
if b[SpaceRoll4] != 0 {
if len(rolls) != 0 {
rolls = append(rolls, ' ')
}
rolls = append(rolls, []byte(fmt.Sprintf("%d", b[SpaceRoll4]))...)
}
return fmt.Sprintf("Board: %s\nVariant: %s\nEntered: %s / %s\nOff: %d / %d\nRolls: %s", board, variant, entered1, entered2, off1, off2, rolls)
}
2023-11-30 07:59:50 +00:00
func (b Board) SetValue(space int, value int8) Board {
b[space] = value
return b
}
// Move moves a checker on the board.
2024-01-08 05:28:21 +00:00
func (b Board) Move(from int8, to int8, player int8) Board {
2023-11-30 07:59:50 +00:00
if b[from] == 0 || (player == 1 && b[from] < 0) || (player == 2 && b[from] > 0) {
log.Panic("illegal move: no from checker", from, to, player)
} else if b[to] != 0 {
if (player == 1 && b[to] == -1) || (player == 2 && b[to] == 1) {
b[to] = 0
2023-11-30 19:17:47 +00:00
if player == 1 {
2023-12-05 20:42:54 +00:00
b[SpaceBarOpponent]--
2023-11-30 19:17:47 +00:00
} else {
2023-12-05 20:42:54 +00:00
b[SpaceBarPlayer]++
2023-11-30 19:17:47 +00:00
}
2023-11-30 07:59:50 +00:00
} else if (player == 1 && b[to] < 0) || (player == 2 && b[to] > 0) {
b.Print()
log.Panic("illegal move: existing checkers at to space", from, to, player, b[to])
}
}
delta := int8(1)
if player == 2 {
delta = -1
2023-11-30 07:59:50 +00:00
}
b[from], b[to] = b[from]-delta, b[to]+delta
2024-01-08 05:28:21 +00:00
if (player == 1 && from == SpaceHomePlayer && b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] == 0) || (player == 2 && from == SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] == 0) {
if player == 1 {
b[SpaceEnteredPlayer] = 1
} else {
b[SpaceEnteredOpponent] = 1
}
}
2023-11-30 07:59:50 +00:00
return b
}
// checkers returns the number of checkers that belong to the spcified player at the provided space.
2024-01-08 05:28:21 +00:00
func checkers(player int8, v int8) int8 {
2023-11-30 07:59:50 +00:00
if player == 1 && v > 0 {
return v
2023-11-30 07:59:50 +00:00
} else if player == 2 && v < 0 {
return v * -1
2023-11-30 07:59:50 +00:00
}
return 0
}
2024-01-08 05:28:21 +00:00
func (b Board) MayBearOff(player int8) bool {
2024-01-07 21:26:03 +00:00
if b[SpaceVariant] != VariantBackgammon && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) {
2023-12-11 01:22:40 +00:00
return false
2024-01-08 05:28:21 +00:00
} else if b[SpaceVariant] == VariantTabula && !b.SecondHalf(player) {
return false
2023-12-11 01:22:40 +00:00
}
2023-11-30 07:59:50 +00:00
barSpace := SpaceBarPlayer
if player == 2 {
barSpace = SpaceBarOpponent
}
if checkers(player, b[barSpace]) != 0 {
2023-11-30 07:59:50 +00:00
return false
}
2024-01-08 05:28:21 +00:00
if b[SpaceVariant] != VariantTabula {
if player == 1 {
for space := 24; space > 6; space-- {
if checkers(player, b[space]) != 0 {
return false
}
}
2024-01-08 05:28:21 +00:00
} else {
for space := 1; space < 19; space++ {
if checkers(player, b[space]) != 0 {
return false
}
}
2023-11-30 07:59:50 +00:00
}
}
return true
}
2024-01-08 05:28:21 +00:00
func (b Board) spaceDiff(player int8, from int8, to int8) int8 {
2023-12-11 01:22:40 +00:00
switch {
case from < 0 || from > 27 || to < 0 || to > 27:
return 0
case to == SpaceBarPlayer || to == SpaceBarOpponent:
return 0
2024-01-08 05:28:21 +00:00
case (from == SpaceHomePlayer || from == SpaceHomeOpponent || from == SpaceBarPlayer || from == SpaceBarOpponent) && (to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomePlayer || to == SpaceHomeOpponent):
2023-12-11 01:22:40 +00:00
return 0
case to == SpaceHomePlayer:
2024-01-08 05:28:21 +00:00
if player == 2 {
return 0
}
if b[SpaceVariant] == VariantTabula {
if (player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0) || !b.SecondHalf(player) {
return 0
}
return 25 - from
}
2023-12-11 01:22:40 +00:00
return from
case to == SpaceHomeOpponent:
2024-01-08 05:28:21 +00:00
if player == 1 {
return 0
}
2023-12-11 01:22:40 +00:00
return 25 - from
case from == SpaceHomePlayer || from == SpaceHomeOpponent:
2024-01-08 05:28:21 +00:00
if (player == 1 && from == SpaceHomeOpponent) || (player == 2 && from == SpaceHomePlayer) {
return 0
}
switch b[SpaceVariant] {
case VariantAceyDeucey:
2023-12-11 01:22:40 +00:00
if player == 1 && from == SpaceHomePlayer && b[SpaceEnteredPlayer] == 0 {
return 25 - to
} else if player == 2 && from == SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0 {
return to
}
2024-01-08 05:28:21 +00:00
case VariantTabula:
if (player == 1 && from != SpaceHomePlayer && b[SpaceEnteredPlayer] == 0) || (player == 2 && from != SpaceHomeOpponent && b[SpaceEnteredOpponent] == 0) {
return 0
}
return to
2023-12-11 01:22:40 +00:00
}
return 0
case from == SpaceBarPlayer:
2024-01-08 05:28:21 +00:00
if player == 2 {
return 0
}
if b[SpaceVariant] == VariantTabula {
return to
}
2023-12-11 01:22:40 +00:00
return 25 - to
case from == SpaceBarOpponent:
2024-01-08 05:28:21 +00:00
if player == 1 {
return 0
}
2023-12-11 01:22:40 +00:00
return to
default:
diff := to - from
if diff < 0 {
return diff * -1
}
return diff
}
}
2023-11-30 07:59:50 +00:00
// HaveRoll returns whether the player has a sufficient die roll for the specified move.
2024-01-08 05:28:21 +00:00
func (b Board) HaveRoll(from int8, to int8, player int8) bool {
2023-12-12 19:08:31 +00:00
barSpace := SpaceBarPlayer
if player == 2 {
barSpace = SpaceBarOpponent
}
if b[barSpace] != 0 && from != barSpace {
return false
}
2024-01-08 05:28:21 +00:00
if b[SpaceVariant] == VariantTabula && to > 12 && to < 25 && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) {
return false
}
delta := b.spaceDiff(player, from, to)
2023-11-30 07:59:50 +00:00
if delta == 0 {
return false
}
if b[SpaceRoll1] == delta || b[SpaceRoll2] == delta || b[SpaceRoll3] == delta || b[SpaceRoll4] == delta {
return true
}
2023-11-30 07:59:50 +00:00
playerDelta := -1
playerHomeEnd := 6
if player == 2 {
playerDelta = 1
playerHomeEnd = 19
}
2024-01-07 21:26:03 +00:00
if b.MayBearOff(player) && b[SpaceVariant] == VariantBackgammon {
2023-11-30 07:59:50 +00:00
allowGreater := true
for checkSpace := 0; checkSpace < 6-int(delta); checkSpace++ {
if checkers(player, b[playerHomeEnd+checkSpace*playerDelta]) != 0 {
2023-11-30 07:59:50 +00:00
allowGreater = false
break
}
}
if allowGreater {
return (b[SpaceRoll1] >= delta || b[SpaceRoll2] >= delta || b[SpaceRoll3] >= delta || b[SpaceRoll4] >= delta)
}
}
return false
2023-11-30 07:59:50 +00:00
}
2024-01-08 05:28:21 +00:00
// UseRoll uses a die roll. UseRoll must be called before making a move.
func (b Board) UseRoll(from int8, to int8, player int8) Board {
delta := b.spaceDiff(player, from, to)
2023-11-30 07:59:50 +00:00
if delta == 0 {
b.Print()
log.Panic("unknown space diff", from, to, player)
}
switch {
case b[SpaceRoll1] == delta:
b[SpaceRoll1] = 0
return b
case b[SpaceRoll2] == delta:
b[SpaceRoll2] = 0
return b
case b[SpaceRoll3] == delta:
b[SpaceRoll3] = 0
return b
case b[SpaceRoll4] == delta:
b[SpaceRoll4] = 0
return b
}
2023-11-30 07:59:50 +00:00
playerDelta := -1
playerHomeEnd := 6
if player == 2 {
playerDelta = 1
playerHomeEnd = 19
}
var allowGreater bool
2024-01-07 21:26:03 +00:00
if b.MayBearOff(player) && b[SpaceVariant] == VariantBackgammon {
2023-11-30 07:59:50 +00:00
allowGreater = true
for checkSpace := int8(0); checkSpace < 6-delta; checkSpace++ {
if checkers(player, b[playerHomeEnd+int(checkSpace)*playerDelta]) != 0 {
2023-11-30 07:59:50 +00:00
allowGreater = false
break
}
}
}
if !allowGreater {
b.Print()
log.Panic(fmt.Sprint(b), "no available roll for move", from, to, player, delta)
}
switch {
case b[SpaceRoll1] >= delta:
b[SpaceRoll1] = 0
case b[SpaceRoll2] >= delta:
b[SpaceRoll2] = 0
case b[SpaceRoll3] >= delta:
b[SpaceRoll3] = 0
case b[SpaceRoll4] >= delta:
b[SpaceRoll4] = 0
default:
b.Print()
log.Panic(fmt.Sprint(b), "no available roll for move", from, to, player, delta)
2023-11-30 07:59:50 +00:00
}
return b
}
2024-01-08 05:28:21 +00:00
func (b Board) _available(player int8) [][2]int8 {
var bearOff int
mayBearOff := func() bool {
if bearOff != 0 {
return bearOff == 1
}
if b.MayBearOff(player) {
bearOff = 1
return true
}
bearOff = 2
return false
}
2023-12-11 01:22:40 +00:00
homeSpace := SpaceHomePlayer
2023-11-30 07:59:50 +00:00
barSpace := SpaceBarPlayer
opponentBarSpace := SpaceBarOpponent
if player == 2 {
2023-12-11 01:22:40 +00:00
homeSpace = SpaceHomeOpponent
2023-11-30 07:59:50 +00:00
barSpace = SpaceBarOpponent
opponentBarSpace = SpaceBarPlayer
}
onBar := b[barSpace] != 0
2023-12-11 01:22:40 +00:00
var moves [][2]int8
2023-12-11 01:22:40 +00:00
2024-01-07 21:26:03 +00:00
if b[SpaceVariant] != VariantBackgammon && ((player == 1 && b[SpaceEnteredPlayer] == 0) || (player == 2 && b[SpaceEnteredOpponent] == 0)) && b[homeSpace] != 0 {
for space := int8(1); space < 25; space++ {
2023-12-12 19:08:31 +00:00
v := b[space]
if ((player == 1 && v >= -1) || (player == 2 && v <= 1)) && b.HaveRoll(homeSpace, space, player) {
moves = append(moves, [2]int8{homeSpace, space})
2023-12-11 01:22:40 +00:00
}
}
}
for from := int8(0); from < 28; from++ {
if from == SpaceHomePlayer || from == SpaceHomeOpponent || from == opponentBarSpace || checkers(player, b[from]) == 0 || (onBar && from != barSpace) {
2023-11-30 07:59:50 +00:00
continue
}
2024-01-08 05:28:21 +00:00
if player == 1 && b[SpaceVariant] != VariantTabula {
for to := int8(0); to < from; to++ {
if to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomeOpponent || (to == SpaceHomePlayer && !mayBearOff()) {
2023-11-30 07:59:50 +00:00
continue
}
v := b[to]
if (player == 1 && v < -1) || (player == 2 && v > 1) || !b.HaveRoll(from, to, player) {
continue
}
moves = append(moves, [2]int8{from, to})
2023-11-30 07:59:50 +00:00
}
} else { // TODO clean up
2023-11-30 19:17:47 +00:00
start := from + 1
2024-01-08 05:28:21 +00:00
if from == SpaceBarPlayer || from == SpaceBarOpponent {
start = 1
2023-11-30 19:17:47 +00:00
}
2024-01-08 05:28:21 +00:00
for i := start; i <= 25; i++ {
to := i
if player == 1 && to == SpaceHomeOpponent {
to = SpaceHomePlayer
} else if player == 2 && to == SpaceHomePlayer {
to = SpaceHomeOpponent
}
if to == SpaceBarPlayer || to == SpaceBarOpponent || (((player == 1 && to == SpaceHomePlayer) || (player == 2 && to == SpaceHomeOpponent)) && !mayBearOff()) {
2023-11-30 07:59:50 +00:00
continue
}
v := b[to]
if (player == 1 && v < -1) || (player == 2 && v > 1) || !b.HaveRoll(from, to, player) {
continue
}
moves = append(moves, [2]int8{from, to})
2023-11-30 07:59:50 +00:00
}
}
}
2023-12-11 01:22:40 +00:00
2023-11-30 07:59:50 +00:00
return moves
}
2023-12-06 22:02:04 +00:00
// Available returns legal moves available.
2024-01-08 05:28:21 +00:00
func (b Board) Available(player int8) ([][4][2]int8, []Board) {
var allMoves [][4][2]int8
2023-12-06 22:02:04 +00:00
movesFound := func(moves [4][2]int8) bool {
for i := range allMoves {
if movesEqual(allMoves[i], moves) {
2023-12-06 22:02:04 +00:00
return true
}
}
return false
}
var boards []Board
a := b._available(player)
maxLen := 1
for _, move := range a {
2024-01-08 05:28:21 +00:00
newBoard := b.UseRoll(move[0], move[1], player).Move(move[0], move[1], player)
2023-12-06 22:02:04 +00:00
newAvailable := newBoard._available(player)
if len(newAvailable) == 0 {
moves := [4][2]int8{move}
2023-12-06 22:02:04 +00:00
if !movesFound(moves) {
allMoves = append(allMoves, moves)
boards = append(boards, newBoard)
}
continue
}
for _, move2 := range newAvailable {
2024-01-08 05:28:21 +00:00
newBoard2 := newBoard.UseRoll(move2[0], move2[1], player).Move(move2[0], move2[1], player)
2023-12-06 22:02:04 +00:00
newAvailable2 := newBoard2._available(player)
if len(newAvailable2) == 0 {
moves := [4][2]int8{move, move2}
2023-12-06 22:02:04 +00:00
if !movesFound(moves) {
allMoves = append(allMoves, moves)
boards = append(boards, newBoard2)
2024-01-08 05:28:21 +00:00
if maxLen <= 2 {
maxLen = 2
}
2023-12-06 22:02:04 +00:00
}
continue
}
for _, move3 := range newAvailable2 {
2024-01-08 05:28:21 +00:00
newBoard3 := newBoard2.UseRoll(move3[0], move3[1], player).Move(move3[0], move3[1], player)
2023-12-06 22:02:04 +00:00
newAvailable3 := newBoard3._available(player)
if len(newAvailable3) == 0 {
moves := [4][2]int8{move, move2, move3}
2023-12-06 22:02:04 +00:00
if !movesFound(moves) {
allMoves = append(allMoves, moves)
boards = append(boards, newBoard3)
2024-01-08 05:28:21 +00:00
if maxLen <= 2 {
maxLen = 3
}
2023-12-06 22:02:04 +00:00
}
continue
}
for _, move4 := range newAvailable3 {
2024-01-08 05:28:21 +00:00
newBoard4 := newBoard3.UseRoll(move4[0], move4[1], player).Move(move4[0], move4[1], player)
moves := [4][2]int8{move, move2, move3, move4}
2023-12-06 22:02:04 +00:00
if !movesFound(moves) {
allMoves = append(allMoves, moves)
boards = append(boards, newBoard4)
maxLen = 4
}
}
}
}
}
var newMoves [][4][2]int8
2023-12-06 22:02:04 +00:00
for i := 0; i < len(allMoves); i++ {
l := 0
2024-01-08 05:28:21 +00:00
if (allMoves[i][3][0] != 0 || allMoves[i][3][1] != 0) && allMoves[i][2][0] == 0 && allMoves[i][2][1] == 0 {
allMoves[i][2][0], allMoves[i][2][1] = allMoves[i][3][0], allMoves[i][3][1]
allMoves[i][2][0], allMoves[i][2][1] = 0, 0
}
if (allMoves[i][2][0] != 0 || allMoves[i][2][1] != 0) && allMoves[i][1][0] == 0 && allMoves[i][1][1] == 0 {
allMoves[i][1][0], allMoves[i][1][1] = allMoves[i][2][0], allMoves[i][2][1]
allMoves[i][2][0], allMoves[i][2][1] = 0, 0
}
if (allMoves[i][1][0] != 0 || allMoves[i][1][1] != 0) && allMoves[i][0][0] == 0 && allMoves[i][0][1] == 0 {
allMoves[i][0][0], allMoves[i][0][1] = allMoves[i][1][0], allMoves[i][1][1]
allMoves[i][1][0], allMoves[i][1][1] = 0, 0
}
for j := 0; j < 4; j++ {
if allMoves[i][j][0] == 0 && allMoves[i][j][1] == 0 {
break
}
l = j + 1
}
if l >= maxLen {
2023-12-06 22:02:04 +00:00
newMoves = append(newMoves, allMoves[i])
}
}
if maxLen == 1 && len(newMoves) > 1 {
moved := b[SpaceRoll1] == 0
if !moved {
moved = b[SpaceRoll2] == 0
}
if !moved && (b[SpaceVariant] == VariantTabula || b[SpaceRoll1] == b[SpaceRoll2]) {
moved = b[SpaceRoll3] == 0
}
if !moved && b[SpaceVariant] != VariantTabula && b[SpaceRoll1] == b[SpaceRoll2] {
moved = b[SpaceRoll4] == 0
}
if !moved {
var highestRoll int8
2024-02-07 19:32:11 +00:00
for _, move := range newMoves {
roll := b.spaceDiff(player, move[0][0], move[0][1])
if roll > highestRoll {
highestRoll = roll
}
}
2024-02-07 19:32:11 +00:00
var highRollMoves [][4][2]int8
for _, move := range newMoves {
roll := b.spaceDiff(player, move[0][0], move[0][1])
if roll != highestRoll {
continue
}
highRollMoves = append(highRollMoves, move)
}
newMoves = highRollMoves
}
}
2023-12-06 22:02:04 +00:00
return newMoves, boards
}
2024-01-18 04:46:01 +00:00
func (b Board) FirstLast(player int8) (playerFirst int8, opponentLast int8) {
playerFirst, opponentLast = -1, -1
2024-01-08 05:28:21 +00:00
if b[SpaceBarPlayer] != 0 || b[SpaceBarOpponent] != 0 || b[SpaceVariant] == VariantTabula {
2024-01-18 04:46:01 +00:00
return playerFirst, opponentLast
2024-01-08 05:28:21 +00:00
} else if b[SpaceVariant] == VariantAceyDeucey && ((b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] != 0) || (b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] != 0)) {
2024-01-18 04:46:01 +00:00
return playerFirst, opponentLast
2023-12-02 18:14:04 +00:00
}
2024-01-18 04:46:01 +00:00
for space := int8(1); space < 25; space++ {
2023-12-02 18:14:04 +00:00
v := b[space]
2023-12-02 19:25:07 +00:00
if v == 0 {
continue
} else if v > 0 {
2023-12-05 20:42:54 +00:00
if space > playerFirst {
2023-12-02 18:14:04 +00:00
playerFirst = space
}
} else {
2024-01-18 04:46:01 +00:00
if opponentLast == -1 {
2023-12-02 18:14:04 +00:00
opponentLast = space
}
}
}
2024-01-18 04:46:01 +00:00
if player == 2 {
return opponentLast, playerFirst
}
return playerFirst, opponentLast
}
func (b Board) Past() bool {
playerFirst, opponentLast := b.FirstLast(1)
if playerFirst == -1 || opponentLast == -1 {
return false
}
2023-12-05 20:42:54 +00:00
return playerFirst < opponentLast
2023-12-02 18:14:04 +00:00
}
2024-01-08 05:28:21 +00:00
func (b Board) SecondHalf(player int8) bool {
if b[SpaceVariant] != VariantTabula {
return false
}
switch player {
case 1:
if b[SpaceBarPlayer] != 0 {
return false
} else if b[SpaceEnteredPlayer] == 0 && b[SpaceHomePlayer] != 0 {
return false
}
case 2:
if b[SpaceBarOpponent] != 0 {
return false
} else if b[SpaceEnteredOpponent] == 0 && b[SpaceHomeOpponent] != 0 {
return false
}
default:
log.Panicf("unknown player: %d", player)
}
for space := 1; space < 13; space++ {
v := b[space]
if (player == 1 && v > 0) || (player == 2 && v < 0) {
return false
}
}
return true
}
func (b Board) Pips(player int8) int {
2023-12-02 19:25:07 +00:00
var pips int
2024-01-07 21:26:03 +00:00
if b[SpaceVariant] != VariantBackgammon {
2023-12-11 01:22:40 +00:00
if player == 1 && b[SpaceEnteredPlayer] == 0 {
pips += int(checkers(player, b[SpaceHomePlayer])) * PseudoPips(player, SpaceHomePlayer, b[SpaceVariant])
2023-12-11 01:22:40 +00:00
} else if player == 2 && b[SpaceEnteredOpponent] == 0 {
pips += int(checkers(player, b[SpaceHomeOpponent])) * PseudoPips(player, SpaceHomeOpponent, b[SpaceVariant])
2023-12-11 01:22:40 +00:00
}
}
2023-11-30 07:59:50 +00:00
if player == 1 {
pips += int(checkers(player, b[SpaceBarPlayer])) * PseudoPips(player, SpaceBarPlayer, b[SpaceVariant])
2023-11-30 07:59:50 +00:00
} else {
pips += int(checkers(player, b[SpaceBarOpponent])) * PseudoPips(player, SpaceBarOpponent, b[SpaceVariant])
2023-11-30 07:59:50 +00:00
}
for space := int8(1); space < 25; space++ {
pips += int(checkers(player, b[space])) * PseudoPips(player, space, b[SpaceVariant])
2023-11-30 07:59:50 +00:00
}
2023-12-02 19:25:07 +00:00
return pips
2023-11-30 07:59:50 +00:00
}
2024-01-08 05:28:21 +00:00
func (b Board) Blots(player int8) int {
2024-01-18 04:46:01 +00:00
_, last := b.FirstLast(player)
2023-12-03 02:18:51 +00:00
o := opponent(player)
2023-11-30 07:59:50 +00:00
var pips int
var pastBlots int
var div int
for space := int8(1); space < 25; space++ {
if checkers(player, b[space]) == 1 {
if last != -1 && ((player == 1 && space < last) || (player == 2 && space > last)) && pastBlots == 0 {
pastBlots++
div = 4
} else {
div = 1
}
v := PseudoPips(o, space, b[SpaceVariant]) / div
if v < 1 {
v = 1
2024-01-18 04:46:01 +00:00
}
pips += v
2023-11-30 07:59:50 +00:00
}
}
return pips
}
2024-01-08 05:28:21 +00:00
func (b Board) evaluate(player int8, hitScore int, a *Analysis) {
pips := b.Pips(player)
2023-12-02 18:14:04 +00:00
score := float64(pips)
blotWeight := WeightBlot
if player == 1 {
var blocks int8
for space := 19; space <= 24; space++ {
if checkers(2, b[space]) > 1 {
blocks++
}
}
switch blocks {
case 6:
blotWeight *= 1.5
case 5:
blotWeight *= 1.25
case 4:
blotWeight *= 1.1
}
}
2023-12-02 18:14:04 +00:00
var blots int
2023-12-02 19:25:07 +00:00
if !a.Past {
2023-12-05 20:42:54 +00:00
blots = b.Blots(player)
score += float64(blots)*blotWeight + float64(hitScore)*WeightHit
2023-12-02 18:14:04 +00:00
}
a.Pips = pips
a.Blots = blots
a.Hits = hitScore
a.PlayerScore = score
2023-12-02 19:25:07 +00:00
a.hitScore = hitScore
}
2024-01-08 05:28:21 +00:00
func (b Board) Evaluation(player int8, hitScore int, moves [4][2]int8) *Analysis {
2023-12-02 18:14:04 +00:00
a := &Analysis{
2023-12-06 22:02:04 +00:00
Board: b,
Moves: moves,
Past: b.Past(),
player: player,
chance: 1,
2023-11-30 07:59:50 +00:00
}
2023-12-02 18:14:04 +00:00
b.evaluate(player, hitScore, a)
return a
2023-11-30 07:59:50 +00:00
}
func (b Board) Analyze(available [][4][2]int8, result *[]*Analysis, skipOpponent bool) {
2023-11-30 07:59:50 +00:00
if len(available) == 0 {
2023-12-11 20:15:26 +00:00
*result = (*result)[:0]
return
2023-11-30 07:59:50 +00:00
}
2024-01-18 04:46:01 +00:00
const priorityScore = -1000000
2023-12-11 20:15:26 +00:00
var reuse []*[]*Analysis
for _, r := range *result {
if r.result != nil {
reuse = append(reuse, r.result)
}
}
*result = (*result)[:0]
reuseLen := len(reuse)
var reuseIndex int
2023-12-06 22:02:04 +00:00
w := &sync.WaitGroup{}
2023-12-06 22:02:04 +00:00
past := b.Past()
2023-12-02 00:19:00 +00:00
w.Add(len(available))
2023-12-06 22:02:04 +00:00
for _, moves := range available {
2023-12-11 20:15:26 +00:00
var r *[]*Analysis
if reuseIndex < reuseLen {
r = reuse[reuseIndex]
2023-12-12 19:08:31 +00:00
*r = (*r)[:0]
2023-12-11 20:15:26 +00:00
reuseIndex++
} else {
v := make([]*Analysis, 0, SubAnalysisBufferSize)
r = &v
}
2023-12-06 22:02:04 +00:00
a := &Analysis{
2023-12-06 23:58:31 +00:00
Board: b,
Moves: moves,
Past: past,
player: 1,
chance: 1,
skipOpp: skipOpponent,
2023-12-11 20:15:26 +00:00
result: r,
2023-12-06 23:58:31 +00:00
resultMutex: &sync.Mutex{},
wg: w,
2023-12-02 19:25:07 +00:00
}
2023-12-11 20:15:26 +00:00
*result = append(*result, a)
2023-12-06 23:58:31 +00:00
analysisQueue <- a
2023-11-30 07:59:50 +00:00
}
2023-12-06 22:02:04 +00:00
w.Wait()
2023-12-11 20:15:26 +00:00
for _, a := range *result {
2023-12-06 23:58:31 +00:00
if a.player == 1 && !a.Past {
var oppPips float64
var oppBlots float64
var oppHits float64
var oppScore float64
var count float64
for _, r := range *a.result {
oppPips += float64(r.Pips)
oppBlots += float64(r.Blots)
oppHits += float64(r.Hits)
oppScore += r.PlayerScore
count++
}
if count == 0 {
a.Score = a.PlayerScore
} else {
a.OppPips = (oppPips / count)
a.OppBlots = (oppBlots / count)
a.OppHits = (oppHits / count)
a.OppScore = (oppScore / count)
score := a.PlayerScore
if !math.IsNaN(oppScore) {
score += a.OppScore * WeightOppScore
}
a.Score = score
}
} else {
a.Score = a.PlayerScore