1079 lines
23 KiB
Go
1079 lines
23 KiB
Go
package bgammon
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"strconv"
|
|
"time"
|
|
|
|
"code.rocket9labs.com/tslocum/tabula"
|
|
)
|
|
|
|
var boardTopBlack = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+")
|
|
var boardBottomBlack = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+")
|
|
|
|
var boardTopWhite = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+")
|
|
var boardBottomWhite = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+")
|
|
|
|
const (
|
|
VariantBackgammon int8 = 0
|
|
VariantAceyDeucey int8 = 1
|
|
VariantTabula int8 = 2
|
|
)
|
|
|
|
type Game struct {
|
|
Started int64
|
|
Ended int64
|
|
|
|
Player1 Player
|
|
Player2 Player
|
|
|
|
Variant int8 // 0 - Backgammon, 1 - Acey-deucey, 2 - Tabula.
|
|
Board []int8
|
|
Turn int8
|
|
|
|
Roll1 int8
|
|
Roll2 int8
|
|
Roll3 int8 // Used in tabula games.
|
|
|
|
Moves [][]int8 // Pending moves.
|
|
Winner int8
|
|
|
|
Points int8 // Points required to win the match.
|
|
DoubleValue int8 // Doubling cube value.
|
|
DoublePlayer int8 // Player that currently posesses the doubling cube.
|
|
DoubleOffered bool // Whether the current player is offering a double.
|
|
|
|
Reroll bool // Used in acey-deucey.
|
|
|
|
partialTurn int8
|
|
partialTime time.Time
|
|
partialHandled bool
|
|
|
|
boardStates [][]int8 // One board state for each move to allow undoing a move.
|
|
enteredStates [][2]bool // Player 1 entered state and Player 2 entered state for each move.
|
|
|
|
// Fields after this point are provided for backwards-compatibility only and will eventually be removed.
|
|
Acey bool // For Boxcars v1.2.1 and earlier.
|
|
}
|
|
|
|
func NewGame(variant int8) *Game {
|
|
g := &Game{
|
|
Variant: variant,
|
|
Board: NewBoard(variant),
|
|
Player1: NewPlayer(1),
|
|
Player2: NewPlayer(2),
|
|
Points: 1,
|
|
DoubleValue: 1,
|
|
}
|
|
if variant == VariantBackgammon {
|
|
g.Player1.Entered = true
|
|
g.Player2.Entered = true
|
|
} else {
|
|
// Set backwards-compatible field.
|
|
g.Acey = true
|
|
}
|
|
return g
|
|
}
|
|
|
|
func (g *Game) Copy(shallow bool) *Game {
|
|
newGame := &Game{
|
|
Started: g.Started,
|
|
Ended: g.Ended,
|
|
|
|
Player1: g.Player1,
|
|
Player2: g.Player2,
|
|
|
|
Variant: g.Variant,
|
|
Board: make([]int8, len(g.Board)),
|
|
Turn: g.Turn,
|
|
Roll1: g.Roll1,
|
|
Roll2: g.Roll2,
|
|
Roll3: g.Roll3,
|
|
Moves: make([][]int8, len(g.Moves)),
|
|
Winner: g.Winner,
|
|
|
|
Points: g.Points,
|
|
DoubleValue: g.DoubleValue,
|
|
DoublePlayer: g.DoublePlayer,
|
|
DoubleOffered: g.DoubleOffered,
|
|
|
|
Reroll: g.Reroll,
|
|
|
|
partialTurn: g.partialTurn,
|
|
partialTime: g.partialTime,
|
|
partialHandled: g.partialHandled,
|
|
}
|
|
copy(newGame.Board, g.Board)
|
|
copy(newGame.Moves, g.Moves)
|
|
if !shallow {
|
|
newGame.boardStates = make([][]int8, len(g.boardStates))
|
|
newGame.enteredStates = make([][2]bool, len(g.enteredStates))
|
|
copy(newGame.boardStates, g.boardStates)
|
|
copy(newGame.enteredStates, g.enteredStates)
|
|
}
|
|
return newGame
|
|
}
|
|
|
|
func (g *Game) PartialTurn() int8 {
|
|
return g.partialTurn
|
|
}
|
|
|
|
func (g *Game) PartialTime() int {
|
|
var delta time.Duration
|
|
if g.partialTime.IsZero() {
|
|
delta = since(g.Started)
|
|
} else {
|
|
delta = time.Since(g.partialTime)
|
|
}
|
|
if delta <= 30*time.Second {
|
|
return 0
|
|
}
|
|
return int(math.Floor(delta.Seconds()))
|
|
}
|
|
|
|
func (g *Game) PartialHandled() bool {
|
|
return g.partialHandled
|
|
}
|
|
|
|
func (g *Game) SetPartialHandled(handled bool) {
|
|
g.partialHandled = handled
|
|
}
|
|
|
|
func (g *Game) NextPartialTurn(player int8) {
|
|
if g.Started == 0 || g.Winner != 0 {
|
|
return
|
|
}
|
|
|
|
delta := g.PartialTime()
|
|
if delta > 0 {
|
|
switch g.partialTurn {
|
|
case 1:
|
|
g.Player1.Inactive += delta
|
|
case 2:
|
|
g.Player2.Inactive += delta
|
|
}
|
|
}
|
|
|
|
g.partialTurn = player
|
|
g.partialTime = time.Now()
|
|
}
|
|
|
|
func (g *Game) NextTurn(reroll bool) {
|
|
if g.Winner != 0 {
|
|
return
|
|
}
|
|
|
|
if !reroll {
|
|
var nextTurn int8 = 1
|
|
if g.Turn == 1 {
|
|
nextTurn = 2
|
|
}
|
|
g.Turn = nextTurn
|
|
}
|
|
|
|
g.NextPartialTurn(g.Turn)
|
|
|
|
g.Roll1, g.Roll2, g.Roll3 = 0, 0, 0
|
|
g.Moves = g.Moves[:0]
|
|
g.boardStates = g.boardStates[:0]
|
|
g.enteredStates = g.enteredStates[:0]
|
|
}
|
|
|
|
func (g *Game) Reset() {
|
|
g.Player1.Inactive = 0
|
|
g.Player2.Inactive = 0
|
|
if g.Variant != VariantBackgammon {
|
|
g.Player1.Entered = false
|
|
g.Player2.Entered = false
|
|
}
|
|
g.Board = NewBoard(g.Variant)
|
|
g.Turn = 0
|
|
g.Roll1 = 0
|
|
g.Roll2 = 0
|
|
g.Roll3 = 0
|
|
g.Moves = nil
|
|
g.DoubleValue = 1
|
|
g.DoublePlayer = 0
|
|
g.DoubleOffered = false
|
|
g.Reroll = false
|
|
g.Winner = 0
|
|
g.boardStates = nil
|
|
g.enteredStates = nil
|
|
g.partialTurn = 0
|
|
g.partialTime = time.Time{}
|
|
}
|
|
|
|
func (g *Game) turnPlayer() Player {
|
|
switch g.Turn {
|
|
case 2:
|
|
return g.Player2
|
|
default:
|
|
return g.Player1
|
|
}
|
|
}
|
|
|
|
func (g *Game) opponentPlayer() Player {
|
|
switch g.Turn {
|
|
case 2:
|
|
return g.Player1
|
|
default:
|
|
return g.Player2
|
|
}
|
|
}
|
|
|
|
func (g *Game) SecondHalf(player int8, local bool) bool {
|
|
if g.Variant != VariantTabula {
|
|
return false
|
|
}
|
|
|
|
b := g.Board
|
|
switch player {
|
|
case 1:
|
|
if b[SpaceBarPlayer] != 0 {
|
|
return false
|
|
} else if !g.Player1.Entered && b[SpaceHomePlayer] != 0 {
|
|
return false
|
|
}
|
|
case 2:
|
|
if b[SpaceBarOpponent] != 0 {
|
|
return false
|
|
} else if !g.Player2.Entered && 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 (g *Game) setEntered() {
|
|
if g.Variant == VariantBackgammon {
|
|
return
|
|
}
|
|
if !g.Player1.Entered && g.Board[SpaceHomePlayer] == 0 {
|
|
g.Player1.Entered = true
|
|
} else if !g.Player2.Entered && g.Board[SpaceHomeOpponent] == 0 {
|
|
g.Player2.Entered = true
|
|
}
|
|
}
|
|
|
|
func (g *Game) addMove(move []int8) bool {
|
|
opponentCheckers := OpponentCheckers(g.Board[move[1]], g.Turn)
|
|
if opponentCheckers > 1 {
|
|
return false
|
|
}
|
|
|
|
var delta int8 = 1
|
|
if g.Turn == 2 {
|
|
delta = -1
|
|
}
|
|
|
|
boardState := make([]int8, len(g.Board))
|
|
copy(boardState, g.Board)
|
|
g.boardStates = append(g.boardStates, boardState)
|
|
g.enteredStates = append(g.enteredStates, [2]bool{g.Player1.Entered, g.Player2.Entered})
|
|
|
|
g.Board[move[0]] -= delta
|
|
if opponentCheckers == 1 { // Hit checker.
|
|
g.Board[move[1]] = delta
|
|
|
|
// Move opponent checker to bar.
|
|
barSpace := SpaceBarOpponent
|
|
if g.Turn == 2 {
|
|
barSpace = SpaceBarPlayer
|
|
}
|
|
g.Board[barSpace] += delta * -1
|
|
} else {
|
|
g.Board[move[1]] += delta
|
|
}
|
|
|
|
g.Moves = append(g.Moves, []int8{move[0], move[1]})
|
|
g.setEntered()
|
|
return true
|
|
}
|
|
|
|
// AddLocalMove adds a move without performing any validation. This is useful when
|
|
// adding a move locally while waiting for an EventBoard response from the server.
|
|
func (g *Game) AddLocalMove(move []int8) bool {
|
|
return g.addMove(move)
|
|
}
|
|
|
|
func (g *Game) ExpandMove(move []int8, currentSpace int8, moves [][]int8, local bool) ([][]int8, bool) {
|
|
l := g.LegalMoves(local)
|
|
var hitMoves [][]int8
|
|
for _, m := range l {
|
|
if OpponentCheckers(g.Board[m[1]], g.Turn) == 1 {
|
|
hitMoves = append(hitMoves, m)
|
|
}
|
|
}
|
|
for i := 0; i < 2; i++ {
|
|
var checkMoves [][]int8
|
|
if i == 0 { // Try moves that will hit an opponent's checker first.
|
|
checkMoves = hitMoves
|
|
} else {
|
|
checkMoves = l
|
|
}
|
|
for _, lm := range checkMoves {
|
|
if lm[0] != currentSpace {
|
|
continue
|
|
}
|
|
|
|
newMoves := make([][]int8, len(moves))
|
|
copy(newMoves, moves)
|
|
newMoves = append(newMoves, []int8{lm[0], lm[1]})
|
|
|
|
if lm[1] == move[1] {
|
|
return newMoves, true
|
|
}
|
|
|
|
currentSpace = lm[1]
|
|
|
|
gc := g.Copy(true)
|
|
gc.addMove(lm)
|
|
m, ok := gc.ExpandMove(move, currentSpace, newMoves, local)
|
|
if ok {
|
|
return m, ok
|
|
}
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// AddMoves adds moves to the game state. Adding a backwards move will remove the equivalent existing move.
|
|
func (g *Game) AddMoves(moves [][]int8, local bool) (bool, [][]int8) {
|
|
if g.Player1.Name == "" || g.Player2.Name == "" || g.Winner != 0 {
|
|
return false, nil
|
|
}
|
|
|
|
var addMoves [][]int8
|
|
var undoMoves [][]int8
|
|
|
|
gameCopy := g.Copy(false)
|
|
|
|
validateOffset := 0
|
|
VALIDATEMOVES:
|
|
for _, move := range moves {
|
|
l := gameCopy.LegalMoves(local)
|
|
for _, lm := range l {
|
|
if lm[0] == move[0] && lm[1] == move[1] {
|
|
addMoves = append(addMoves, []int8{move[0], move[1]})
|
|
continue VALIDATEMOVES
|
|
}
|
|
}
|
|
|
|
if len(gameCopy.Moves) > 0 {
|
|
i := len(gameCopy.Moves) - 1 - validateOffset
|
|
if i < 0 {
|
|
return false, nil
|
|
}
|
|
gameMove := gameCopy.Moves[i]
|
|
if move[0] == gameMove[1] && move[1] == gameMove[0] {
|
|
undoMoves = append(undoMoves, []int8{gameMove[1], gameMove[0]})
|
|
validateOffset++
|
|
continue VALIDATEMOVES
|
|
}
|
|
}
|
|
|
|
expandedMoves, ok := g.ExpandMove(move, move[0], nil, local)
|
|
if ok {
|
|
for _, expanded := range expandedMoves {
|
|
addMoves = append(addMoves, []int8{expanded[0], expanded[1]})
|
|
}
|
|
continue VALIDATEMOVES
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if len(addMoves) != 0 && len(undoMoves) != 0 {
|
|
return false, nil
|
|
}
|
|
|
|
var checkWin bool
|
|
ADDMOVES:
|
|
for _, move := range addMoves {
|
|
l := gameCopy.LegalMoves(local)
|
|
for _, lm := range l {
|
|
if lm[0] == move[0] && lm[1] == move[1] {
|
|
if !gameCopy.addMove(move) {
|
|
return false, nil
|
|
}
|
|
|
|
if move[1] == SpaceHomePlayer || move[1] == SpaceHomeOpponent {
|
|
checkWin = true
|
|
}
|
|
continue ADDMOVES
|
|
}
|
|
}
|
|
}
|
|
for _, move := range undoMoves {
|
|
if len(gameCopy.Moves) > 0 {
|
|
i := len(gameCopy.Moves) - 1
|
|
if i < 0 {
|
|
return false, nil
|
|
}
|
|
gameMove := gameCopy.Moves[i]
|
|
if move[0] == gameMove[1] && move[1] == gameMove[0] {
|
|
gameCopy.Moves = gameCopy.Moves[:i]
|
|
if !local {
|
|
copy(gameCopy.Board, gameCopy.boardStates[i])
|
|
gameCopy.Player1.Entered = gameCopy.enteredStates[i][0]
|
|
gameCopy.Player2.Entered = gameCopy.enteredStates[i][1]
|
|
gameCopy.boardStates = gameCopy.boardStates[:i]
|
|
gameCopy.enteredStates = gameCopy.enteredStates[:i]
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
g.Board = append(g.Board[:0], gameCopy.Board...)
|
|
g.Moves = gameCopy.Moves
|
|
g.Player1.Entered, g.Player2.Entered = gameCopy.Player1.Entered, gameCopy.Player2.Entered
|
|
g.boardStates = gameCopy.boardStates
|
|
g.enteredStates = gameCopy.enteredStates
|
|
|
|
if checkWin {
|
|
entered := g.Player1.Entered
|
|
if !local && g.Turn == 2 {
|
|
entered = g.Player2.Entered
|
|
}
|
|
|
|
var foundChecker bool
|
|
if g.Variant != VariantBackgammon && !entered {
|
|
foundChecker = true
|
|
} else {
|
|
for space := 1; space <= 24; space++ {
|
|
if PlayerCheckers(g.Board[space], g.Turn) != 0 {
|
|
foundChecker = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundChecker {
|
|
g.Winner = g.Turn
|
|
}
|
|
}
|
|
|
|
if len(addMoves) > 0 {
|
|
return true, addMoves
|
|
} else {
|
|
return true, undoMoves
|
|
}
|
|
}
|
|
|
|
func (g *Game) DiceRolls() []int8 {
|
|
rolls := []int8{
|
|
g.Roll1,
|
|
g.Roll2,
|
|
}
|
|
if g.Variant == VariantTabula {
|
|
rolls = append(rolls, g.Roll3)
|
|
} else if g.Roll1 == g.Roll2 {
|
|
rolls = append(rolls, g.Roll1, g.Roll2)
|
|
}
|
|
|
|
useDiceRoll := func(from, to int8) bool {
|
|
if to == SpaceHomePlayer || to == SpaceHomeOpponent {
|
|
needRoll := from
|
|
if to == SpaceHomeOpponent || g.Variant == VariantTabula {
|
|
needRoll = 25 - from
|
|
}
|
|
for i, roll := range rolls {
|
|
if roll == needRoll {
|
|
rolls = append(rolls[:i], rolls[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
for i, roll := range rolls {
|
|
if roll > needRoll {
|
|
rolls = append(rolls[:i], rolls[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
diff := SpaceDiff(from, to, g.Variant)
|
|
for i, roll := range rolls {
|
|
if roll == diff {
|
|
rolls = append(rolls[:i], rolls[i+1:]...)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
for _, move := range g.Moves {
|
|
if !useDiceRoll(move[0], move[1]) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return rolls
|
|
}
|
|
|
|
func (g *Game) HaveDiceRoll(from int8, to int8) int8 {
|
|
if g.Variant == VariantTabula && to > 12 && to < 25 && ((g.Turn == 1 && !g.Player1.Entered) || (g.Turn == 2 && !g.Player2.Entered)) {
|
|
return 0
|
|
} else if (to == SpaceHomePlayer || to == SpaceHomeOpponent) && !g.MayBearOff(g.Turn, false) {
|
|
return 0
|
|
}
|
|
diff := SpaceDiff(from, to, g.Variant)
|
|
if diff == 0 {
|
|
return 0
|
|
}
|
|
var c int8
|
|
for _, roll := range g.DiceRolls() {
|
|
if roll == diff {
|
|
c++
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (g *Game) HaveBearOffDiceRoll(diff int8) int8 {
|
|
if diff == 0 {
|
|
return 0
|
|
}
|
|
var c int8
|
|
for _, roll := range g.DiceRolls() {
|
|
if roll == diff || (roll > diff && g.Variant == VariantBackgammon) {
|
|
c++
|
|
}
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (g *Game) LegalMoves(local bool) [][]int8 {
|
|
if g.Turn == 0 {
|
|
return nil
|
|
}
|
|
b, ok := g.TabulaBoard()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
barSpace := SpaceBarPlayer
|
|
if g.Turn == 2 {
|
|
barSpace = SpaceBarOpponent
|
|
}
|
|
onBar := g.Board[barSpace] != 0
|
|
available, _ := b.Available(g.Turn)
|
|
mayBearOff := b.MayBearOff(g.Turn)
|
|
var moves [][]int8
|
|
for i := range available {
|
|
for j := range available[i] {
|
|
if available[i][j][0] == 0 && available[i][j][1] == 0 {
|
|
break
|
|
}
|
|
if (!onBar || (onBar && available[i][j][0] == barSpace)) && ((available[i][j][1] != tabula.SpaceHomePlayer && available[i][j][1] != tabula.SpaceHomeOpponent) || mayBearOff) && PlayerCheckers(g.Board[available[i][j][0]], g.Turn) != 0 {
|
|
var found bool
|
|
for _, m := range moves {
|
|
if m[0] == available[i][j][0] && m[1] == available[i][j][1] {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found && b.HaveRoll(available[i][j][0], available[i][j][1], g.Turn) {
|
|
moves = append(moves, []int8{available[i][j][0], available[i][j][1]})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return moves
|
|
}
|
|
|
|
// MayBearOff returns whether the provided player may bear checkers off of the board.
|
|
func (g *Game) MayBearOff(player int8, local bool) bool {
|
|
if PlayerCheckers(g.Board[SpaceBarPlayer], player) > 0 || PlayerCheckers(g.Board[SpaceBarOpponent], player) > 0 {
|
|
return false
|
|
} else if (player == 1 && !g.Player1.Entered) || (player == 2 && !g.Player2.Entered) {
|
|
return false
|
|
} else if g.Variant == VariantTabula {
|
|
return g.SecondHalf(player, local)
|
|
}
|
|
|
|
homeStart, homeEnd := int8(1), int8(6)
|
|
if !local {
|
|
homeStart, homeEnd = HomeRange(player, g.Variant)
|
|
homeStart, homeEnd = minInt(homeStart, homeEnd), maxInt(homeStart, homeEnd)
|
|
}
|
|
for i := int8(1); i <= 24; i++ {
|
|
if (i < homeStart || i > homeEnd) && PlayerCheckers(g.Board[i], player) > 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (g *Game) RenderSpace(player int8, space int8, spaceValue int8, legalMoves [][]int8) []byte {
|
|
var playerColor = "x"
|
|
var opponentColor = "o"
|
|
if player == 2 {
|
|
playerColor = "o"
|
|
opponentColor = "x"
|
|
}
|
|
|
|
var pieceColor string
|
|
value := g.Board[space]
|
|
if space == SpaceBarPlayer {
|
|
pieceColor = playerColor
|
|
} else if space == SpaceBarOpponent {
|
|
pieceColor = opponentColor
|
|
} else {
|
|
if value < 0 {
|
|
pieceColor = "o"
|
|
} else if value > 0 {
|
|
pieceColor = "x"
|
|
} else {
|
|
pieceColor = playerColor
|
|
}
|
|
}
|
|
|
|
abs := value
|
|
if value < 0 {
|
|
abs = value * -1
|
|
}
|
|
|
|
top := space > 12
|
|
if player == 2 {
|
|
top = !top
|
|
}
|
|
|
|
var firstDigit int8 = 4
|
|
var secondDigit int8 = 5
|
|
if !top {
|
|
firstDigit = 5
|
|
secondDigit = 4
|
|
}
|
|
|
|
var firstNumeral string
|
|
var secondNumeral string
|
|
if abs > 5 {
|
|
if abs > 9 {
|
|
firstNumeral = "1"
|
|
} else {
|
|
firstNumeral = strconv.Itoa(int(abs))
|
|
}
|
|
if abs > 9 {
|
|
secondNumeral = strconv.Itoa(int(abs) - 10)
|
|
}
|
|
|
|
if spaceValue == firstDigit && (!top || abs > 9) {
|
|
pieceColor = firstNumeral
|
|
} else if spaceValue == secondDigit && abs > 9 {
|
|
pieceColor = secondNumeral
|
|
} else if top && spaceValue == secondDigit {
|
|
pieceColor = firstNumeral
|
|
}
|
|
}
|
|
|
|
if abs > 5 {
|
|
abs = 5
|
|
}
|
|
|
|
var r []byte
|
|
if abs > 0 && spaceValue <= abs {
|
|
r = []byte(pieceColor)
|
|
} else {
|
|
r = []byte(" ")
|
|
}
|
|
return append(append([]byte(" "), r...), ' ')
|
|
}
|
|
|
|
func (g *Game) BoardState(player int8, local bool) []byte {
|
|
var t bytes.Buffer
|
|
|
|
playerRating := "0"
|
|
opponentRating := "0"
|
|
|
|
var white bool
|
|
if player == 2 {
|
|
white = true
|
|
}
|
|
|
|
var opponentName = g.Player2.Name
|
|
var playerName = g.Player1.Name
|
|
if playerName == "" {
|
|
playerName = "Waiting..."
|
|
}
|
|
if opponentName == "" {
|
|
opponentName = "Waiting..."
|
|
}
|
|
if white {
|
|
playerName, opponentName = opponentName, playerName
|
|
}
|
|
|
|
var playerColor = "x"
|
|
var opponentColor = "o"
|
|
playerRoll := g.Roll1
|
|
opponentRoll := g.Roll2
|
|
if white {
|
|
playerColor = "o"
|
|
opponentColor = "x"
|
|
playerRoll = g.Roll2
|
|
opponentRoll = g.Roll1
|
|
}
|
|
|
|
if white {
|
|
t.Write(boardTopWhite)
|
|
} else {
|
|
t.Write(boardTopBlack)
|
|
}
|
|
t.WriteString(" ")
|
|
t.WriteByte('\n')
|
|
|
|
legalMoves := g.LegalMoves(local)
|
|
space := func(row int8, col int8) []byte {
|
|
var spaceValue int8 = row + 1
|
|
if row > 5 {
|
|
spaceValue = 5 - (row - 6)
|
|
}
|
|
|
|
if col == -1 {
|
|
if row <= 4 {
|
|
return g.RenderSpace(player, SpaceBarOpponent, spaceValue, legalMoves)
|
|
}
|
|
return g.RenderSpace(player, SpaceBarPlayer, spaceValue, legalMoves)
|
|
}
|
|
|
|
var space int8
|
|
if white {
|
|
space = 24 - col
|
|
if row > 5 {
|
|
space = 1 + col
|
|
}
|
|
} else {
|
|
space = 13 + col
|
|
if row > 5 {
|
|
space = 12 - col
|
|
}
|
|
}
|
|
|
|
if row == 5 {
|
|
return []byte(" ")
|
|
}
|
|
|
|
return g.RenderSpace(player, space, spaceValue, legalMoves)
|
|
}
|
|
|
|
const verticalBar rune = '│'
|
|
for i := int8(0); i < 11; i++ {
|
|
t.WriteRune(verticalBar)
|
|
t.Write([]byte(""))
|
|
for j := int8(0); j < 12; j++ {
|
|
t.Write(space(i, j))
|
|
|
|
if j == 5 {
|
|
t.WriteRune(verticalBar)
|
|
t.Write(space(i, -1))
|
|
t.WriteRune(verticalBar)
|
|
}
|
|
}
|
|
|
|
t.Write([]byte("" + string(verticalBar) + " "))
|
|
|
|
if i == 0 {
|
|
t.Write([]byte(opponentColor + " " + opponentName + " (" + opponentRating + ")"))
|
|
if g.Board[SpaceHomeOpponent] != 0 {
|
|
v := g.Board[SpaceHomeOpponent]
|
|
if v < 0 {
|
|
v *= -1
|
|
}
|
|
t.Write([]byte(fmt.Sprintf(" %d off", v)))
|
|
}
|
|
} else if i == 2 {
|
|
if g.Turn == 0 {
|
|
if g.Player1.Name != "" && g.Player2.Name != "" {
|
|
if opponentRoll != 0 {
|
|
t.Write([]byte(fmt.Sprintf(" %d", opponentRoll)))
|
|
} else {
|
|
t.Write([]byte(" -"))
|
|
}
|
|
}
|
|
} else if g.Turn != player {
|
|
if g.Roll1 > 0 {
|
|
t.Write([]byte(fmt.Sprintf(" %d %d ", g.Roll1, g.Roll2)))
|
|
if g.Roll3 != 0 {
|
|
t.Write([]byte(fmt.Sprintf("%d ", g.Roll3)))
|
|
}
|
|
} else if opponentName != "" {
|
|
t.Write([]byte(" - - "))
|
|
}
|
|
}
|
|
} else if i == 8 {
|
|
if g.Turn == 0 {
|
|
if g.Player1.Name != "" && g.Player2.Name != "" {
|
|
if playerRoll != 0 {
|
|
t.Write([]byte(fmt.Sprintf(" %d", playerRoll)))
|
|
} else {
|
|
t.Write([]byte(" -"))
|
|
}
|
|
}
|
|
} else if g.Turn == player {
|
|
if g.Roll1 > 0 {
|
|
t.Write([]byte(fmt.Sprintf(" %d %d ", g.Roll1, g.Roll2)))
|
|
if g.Roll3 != 0 {
|
|
t.Write([]byte(fmt.Sprintf("%d ", g.Roll3)))
|
|
}
|
|
} else if playerName != "" {
|
|
t.Write([]byte(" - - "))
|
|
}
|
|
}
|
|
} else if i == 10 {
|
|
t.Write([]byte(playerColor + " " + playerName + " (" + playerRating + ")"))
|
|
if g.Board[SpaceHomePlayer] != 0 {
|
|
v := g.Board[SpaceHomePlayer]
|
|
if v < 0 {
|
|
v *= -1
|
|
}
|
|
t.Write([]byte(fmt.Sprintf(" %d off", v)))
|
|
}
|
|
}
|
|
|
|
t.Write([]byte(" "))
|
|
t.WriteByte('\n')
|
|
}
|
|
|
|
if white {
|
|
t.Write(boardBottomWhite)
|
|
} else {
|
|
t.Write(boardBottomBlack)
|
|
}
|
|
t.WriteString(" \n")
|
|
|
|
return t.Bytes()
|
|
}
|
|
|
|
func SpaceDiff(from int8, to int8, variant int8) int8 {
|
|
switch {
|
|
case from < 0 || from > 27 || to < 0 || to > 27:
|
|
return 0
|
|
case to == SpaceBarPlayer || to == SpaceBarOpponent:
|
|
return 0
|
|
case (from == SpaceBarPlayer || from == SpaceBarOpponent) && (to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomePlayer || to == SpaceHomeOpponent):
|
|
return 0
|
|
case to == SpaceHomePlayer:
|
|
if variant == VariantTabula {
|
|
return 25 - from
|
|
}
|
|
return from
|
|
case to == SpaceHomeOpponent:
|
|
return 25 - from
|
|
case from == SpaceHomePlayer || from == SpaceHomeOpponent:
|
|
switch variant {
|
|
case VariantAceyDeucey:
|
|
if from == SpaceHomePlayer {
|
|
return 25 - to
|
|
} else {
|
|
return to
|
|
}
|
|
case VariantTabula:
|
|
return to
|
|
}
|
|
return 0
|
|
case from == SpaceBarPlayer:
|
|
if variant == VariantTabula {
|
|
return to
|
|
}
|
|
return 25 - to
|
|
case from == SpaceBarOpponent:
|
|
return to
|
|
default:
|
|
diff := to - from
|
|
if diff < 0 {
|
|
return diff * -1
|
|
}
|
|
return diff
|
|
}
|
|
}
|
|
|
|
func IterateSpaces(from int8, to int8, variant int8, f func(space int8, spaceCount int8)) {
|
|
if from == to || from < 0 || from > 25 || to < 0 || to > 25 {
|
|
return
|
|
} else if variant == VariantBackgammon {
|
|
if from == 0 {
|
|
from = 1
|
|
} else if from == 25 {
|
|
from = 24
|
|
}
|
|
}
|
|
var i int8 = 1
|
|
if to > from {
|
|
for space := from; space <= to; space++ {
|
|
f(space, i)
|
|
i++
|
|
}
|
|
} else {
|
|
for space := from; space >= to; space-- {
|
|
f(space, i)
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
func PlayerCheckers(checkers int8, player int8) int8 {
|
|
if player == 1 {
|
|
if checkers > 0 {
|
|
return checkers
|
|
}
|
|
return 0
|
|
} else {
|
|
if checkers < 0 {
|
|
return checkers * -1
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func OpponentCheckers(checkers int8, player int8) int8 {
|
|
if player == 2 {
|
|
if checkers > 0 {
|
|
return checkers
|
|
}
|
|
return 0
|
|
} else {
|
|
if checkers < 0 {
|
|
return checkers * -1
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func FlipSpace(space int8, player int8, variant int8) int8 {
|
|
if player == 1 {
|
|
return space
|
|
}
|
|
if space < 1 || space > 24 {
|
|
switch space {
|
|
case SpaceHomePlayer:
|
|
return SpaceHomeOpponent
|
|
case SpaceHomeOpponent:
|
|
return SpaceHomePlayer
|
|
case SpaceBarPlayer:
|
|
return SpaceBarOpponent
|
|
case SpaceBarOpponent:
|
|
return SpaceBarPlayer
|
|
default:
|
|
return -1
|
|
}
|
|
}
|
|
if variant == VariantTabula {
|
|
return space
|
|
}
|
|
return 24 - space + 1
|
|
}
|
|
|
|
func FlipMoves(moves [][]int8, player int8, variant int8) [][]int8 {
|
|
m := make([][]int8, len(moves))
|
|
for i := range moves {
|
|
m[i] = []int8{FlipSpace(moves[i][0], player, variant), FlipSpace(moves[i][1], player, variant)}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func FormatSpace(space int8) []byte {
|
|
if space >= 1 && space <= 24 {
|
|
return []byte(strconv.Itoa(int(space)))
|
|
} else if space == SpaceBarPlayer || space == SpaceBarOpponent {
|
|
return []byte("bar")
|
|
} else if space == SpaceHomePlayer || space == SpaceHomeOpponent {
|
|
return []byte("off")
|
|
}
|
|
return []byte("?")
|
|
}
|
|
|
|
func FormatMoves(moves [][]int8) []byte {
|
|
if len(moves) == 0 {
|
|
return []byte("none")
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
for i := range moves {
|
|
if i != 0 {
|
|
out.WriteByte(' ')
|
|
}
|
|
out.Write([]byte(fmt.Sprintf("%s/%s", FormatSpace(moves[i][0]), FormatSpace(moves[i][1]))))
|
|
}
|
|
return out.Bytes()
|
|
}
|
|
|
|
func FormatAndFlipMoves(moves [][]int8, player int8, variant int8) []byte {
|
|
return FormatMoves(FlipMoves(moves, player, variant))
|
|
}
|
|
|
|
func ValidSpace(space int8) bool {
|
|
return space >= 0 && space <= 27
|
|
}
|
|
|
|
func (g *Game) TabulaBoard() (tabula.Board, bool) {
|
|
var roll1, roll2, roll3, roll4 int8
|
|
roll1, roll2 = int8(g.Roll1), int8(g.Roll2)
|
|
if g.Variant == VariantTabula {
|
|
roll3 = int8(g.Roll3)
|
|
} else if roll1 == roll2 {
|
|
roll3, roll4 = int8(g.Roll1), int8(g.Roll2)
|
|
}
|
|
entered1, entered2 := int8(1), int8(1)
|
|
if g.Variant != VariantBackgammon {
|
|
if !g.Player1.Entered {
|
|
entered1 = 0
|
|
}
|
|
if !g.Player2.Entered {
|
|
entered2 = 0
|
|
}
|
|
}
|
|
b := g.Board
|
|
tb := tabula.Board{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19], b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], roll1, roll2, roll3, roll4, entered1, entered2, g.Variant}
|
|
for _, move := range g.Moves {
|
|
diff := SpaceDiff(move[0], move[1], g.Variant)
|
|
if diff == 0 {
|
|
return tabula.Board{}, false
|
|
}
|
|
if tb[tabula.SpaceRoll1] == diff {
|
|
tb[tabula.SpaceRoll1] = 0
|
|
continue
|
|
} else if tb[tabula.SpaceRoll2] == diff {
|
|
tb[tabula.SpaceRoll2] = 0
|
|
continue
|
|
} else if tb[tabula.SpaceRoll3] == diff {
|
|
tb[tabula.SpaceRoll3] = 0
|
|
continue
|
|
} else if tb[tabula.SpaceRoll4] == diff {
|
|
tb[tabula.SpaceRoll4] = 0
|
|
continue
|
|
}
|
|
var highest = tabula.SpaceRoll1
|
|
if tb[tabula.SpaceRoll2] > tb[tabula.SpaceRoll1] {
|
|
highest = tabula.SpaceRoll2
|
|
}
|
|
if tb[tabula.SpaceRoll3] > tb[highest] {
|
|
highest = tabula.SpaceRoll3
|
|
}
|
|
if tb[tabula.SpaceRoll4] > tb[highest] {
|
|
highest = tabula.SpaceRoll4
|
|
}
|
|
if tb[highest] < diff {
|
|
return tabula.Board{}, false
|
|
}
|
|
tb[highest] = 0
|
|
}
|
|
return tb, true
|
|
}
|
|
|
|
func since(timestamp int64) time.Duration {
|
|
return time.Since(time.Unix(timestamp, 0))
|
|
}
|