...

Source file src/code.rocket9labs.com/tslocum/bgammon/game.go

Documentation: code.rocket9labs.com/tslocum/bgammon

     1  package bgammon
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"math"
     8  	"strconv"
     9  	"time"
    10  
    11  	"code.rocket9labs.com/tslocum/tabula"
    12  )
    13  
    14  var boardTopBlack = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+")
    15  var boardBottomBlack = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+")
    16  
    17  var boardTopWhite = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+")
    18  var boardBottomWhite = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+")
    19  
    20  const (
    21  	VariantBackgammon int8 = 0
    22  	VariantAceyDeucey int8 = 1
    23  	VariantTabula     int8 = 2
    24  )
    25  
    26  type Game struct {
    27  	Started time.Time
    28  	Ended   time.Time
    29  
    30  	Player1 Player
    31  	Player2 Player
    32  
    33  	Variant int8 // 0 - Backgammon, 1 - Acey-deucey, 2 - Tabula.
    34  	Board   []int8
    35  	Turn    int8
    36  
    37  	Roll1 int8
    38  	Roll2 int8
    39  	Roll3 int8 // Used in tabula games.
    40  
    41  	Moves  [][]int8 // Pending moves.
    42  	Winner int8
    43  
    44  	Points        int8 // Points required to win the match.
    45  	DoubleValue   int8 // Doubling cube value.
    46  	DoublePlayer  int8 // Player that currently posesses the doubling cube.
    47  	DoubleOffered bool // Whether the current player is offering a double.
    48  
    49  	Reroll bool // Used in acey-deucey.
    50  
    51  	partialTurn    int8
    52  	partialTime    time.Time
    53  	partialHandled bool
    54  
    55  	boardStates   [][]int8  // One board state for each move to allow undoing a move.
    56  	enteredStates [][2]bool // Player 1 entered state and Player 2 entered state for each move.
    57  
    58  	// Fields after this point are provided for backwards-compatibility only and will eventually be removed.
    59  	Acey bool // For Boxcars v1.2.1 and earlier.
    60  }
    61  
    62  func NewGame(variant int8) *Game {
    63  	g := &Game{
    64  		Variant:     variant,
    65  		Board:       NewBoard(variant),
    66  		Player1:     NewPlayer(1),
    67  		Player2:     NewPlayer(2),
    68  		Points:      1,
    69  		DoubleValue: 1,
    70  	}
    71  	if variant == VariantBackgammon {
    72  		g.Player1.Entered = true
    73  		g.Player2.Entered = true
    74  	} else {
    75  		// Set backwards-compatible field.
    76  		g.Acey = true
    77  	}
    78  	return g
    79  }
    80  
    81  func (g *Game) Copy(shallow bool) *Game {
    82  	newGame := &Game{
    83  		Started: g.Started,
    84  		Ended:   g.Ended,
    85  
    86  		Player1: g.Player1,
    87  		Player2: g.Player2,
    88  
    89  		Variant: g.Variant,
    90  		Board:   make([]int8, len(g.Board)),
    91  		Turn:    g.Turn,
    92  		Roll1:   g.Roll1,
    93  		Roll2:   g.Roll2,
    94  		Roll3:   g.Roll3,
    95  		Moves:   make([][]int8, len(g.Moves)),
    96  		Winner:  g.Winner,
    97  
    98  		Points:        g.Points,
    99  		DoubleValue:   g.DoubleValue,
   100  		DoublePlayer:  g.DoublePlayer,
   101  		DoubleOffered: g.DoubleOffered,
   102  
   103  		Reroll: g.Reroll,
   104  
   105  		partialTurn:    g.partialTurn,
   106  		partialTime:    g.partialTime,
   107  		partialHandled: g.partialHandled,
   108  	}
   109  	copy(newGame.Board, g.Board)
   110  	copy(newGame.Moves, g.Moves)
   111  	if !shallow {
   112  		newGame.boardStates = make([][]int8, len(g.boardStates))
   113  		newGame.enteredStates = make([][2]bool, len(g.enteredStates))
   114  		copy(newGame.boardStates, g.boardStates)
   115  		copy(newGame.enteredStates, g.enteredStates)
   116  	}
   117  	return newGame
   118  }
   119  
   120  func (g *Game) PartialTurn() int8 {
   121  	return g.partialTurn
   122  }
   123  
   124  func (g *Game) PartialTime() int {
   125  	var delta time.Duration
   126  	if g.partialTime.IsZero() {
   127  		delta = time.Since(g.Started)
   128  	} else {
   129  		delta = time.Since(g.partialTime)
   130  	}
   131  	if delta <= 30*time.Second {
   132  		return 0
   133  	}
   134  	return int(math.Floor(delta.Seconds()))
   135  }
   136  
   137  func (g *Game) PartialHandled() bool {
   138  	return g.partialHandled
   139  }
   140  
   141  func (g *Game) SetPartialHandled(handled bool) {
   142  	g.partialHandled = handled
   143  }
   144  
   145  func (g *Game) NextPartialTurn(player int8) {
   146  	if g.Started.IsZero() || g.Winner != 0 {
   147  		return
   148  	}
   149  
   150  	delta := g.PartialTime()
   151  	if delta > 0 {
   152  		switch g.partialTurn {
   153  		case 1:
   154  			g.Player1.Inactive += delta
   155  		case 2:
   156  			g.Player2.Inactive += delta
   157  		}
   158  	}
   159  
   160  	g.partialTurn = player
   161  	g.partialTime = time.Now()
   162  }
   163  
   164  func (g *Game) NextTurn(reroll bool) {
   165  	if g.Winner != 0 {
   166  		return
   167  	}
   168  
   169  	if !reroll {
   170  		var nextTurn int8 = 1
   171  		if g.Turn == 1 {
   172  			nextTurn = 2
   173  		}
   174  		g.Turn = nextTurn
   175  	}
   176  
   177  	g.NextPartialTurn(g.Turn)
   178  
   179  	g.Roll1, g.Roll2, g.Roll3 = 0, 0, 0
   180  	g.Moves = g.Moves[:0]
   181  	g.boardStates = g.boardStates[:0]
   182  	g.enteredStates = g.enteredStates[:0]
   183  }
   184  
   185  func (g *Game) Reset() {
   186  	g.Player1.Inactive = 0
   187  	g.Player2.Inactive = 0
   188  	if g.Variant != VariantBackgammon {
   189  		g.Player1.Entered = false
   190  		g.Player2.Entered = false
   191  	}
   192  	g.Board = NewBoard(g.Variant)
   193  	g.Turn = 0
   194  	g.Roll1 = 0
   195  	g.Roll2 = 0
   196  	g.Roll3 = 0
   197  	g.Moves = nil
   198  	g.DoubleValue = 1
   199  	g.DoublePlayer = 0
   200  	g.DoubleOffered = false
   201  	g.Reroll = false
   202  	g.Winner = 0
   203  	g.boardStates = nil
   204  	g.enteredStates = nil
   205  	g.partialTurn = 0
   206  	g.partialTime = time.Time{}
   207  }
   208  
   209  func (g *Game) turnPlayer() Player {
   210  	switch g.Turn {
   211  	case 2:
   212  		return g.Player2
   213  	default:
   214  		return g.Player1
   215  	}
   216  }
   217  
   218  func (g *Game) opponentPlayer() Player {
   219  	switch g.Turn {
   220  	case 2:
   221  		return g.Player1
   222  	default:
   223  		return g.Player2
   224  	}
   225  }
   226  
   227  func (g *Game) SecondHalf(player int8, local bool) bool {
   228  	if g.Variant != VariantTabula {
   229  		return false
   230  	}
   231  
   232  	b := g.Board
   233  	switch player {
   234  	case 1:
   235  		if b[SpaceBarPlayer] != 0 {
   236  			return false
   237  		} else if !g.Player1.Entered && b[SpaceHomePlayer] != 0 {
   238  			return false
   239  		}
   240  	case 2:
   241  		if b[SpaceBarOpponent] != 0 {
   242  			return false
   243  		} else if !g.Player2.Entered && b[SpaceHomeOpponent] != 0 {
   244  			return false
   245  		}
   246  	default:
   247  		log.Panicf("unknown player: %d", player)
   248  	}
   249  
   250  	for space := 1; space < 13; space++ {
   251  		v := b[space]
   252  		if (player == 1 && v > 0) || (player == 2 && v < 0) {
   253  			return false
   254  		}
   255  	}
   256  
   257  	return true
   258  }
   259  
   260  func (g *Game) setEntered() {
   261  	if g.Variant == VariantBackgammon {
   262  		return
   263  	}
   264  	if !g.Player1.Entered && g.Board[SpaceHomePlayer] == 0 {
   265  		g.Player1.Entered = true
   266  	} else if !g.Player2.Entered && g.Board[SpaceHomeOpponent] == 0 {
   267  		g.Player2.Entered = true
   268  	}
   269  }
   270  
   271  func (g *Game) addMove(move []int8) bool {
   272  	opponentCheckers := OpponentCheckers(g.Board[move[1]], g.Turn)
   273  	if opponentCheckers > 1 {
   274  		return false
   275  	}
   276  
   277  	var delta int8 = 1
   278  	if g.Turn == 2 {
   279  		delta = -1
   280  	}
   281  
   282  	boardState := make([]int8, len(g.Board))
   283  	copy(boardState, g.Board)
   284  	g.boardStates = append(g.boardStates, boardState)
   285  	g.enteredStates = append(g.enteredStates, [2]bool{g.Player1.Entered, g.Player2.Entered})
   286  
   287  	g.Board[move[0]] -= delta
   288  	if opponentCheckers == 1 { // Hit checker.
   289  		g.Board[move[1]] = delta
   290  
   291  		// Move opponent checker to bar.
   292  		barSpace := SpaceBarOpponent
   293  		if g.Turn == 2 {
   294  			barSpace = SpaceBarPlayer
   295  		}
   296  		g.Board[barSpace] += delta * -1
   297  	} else {
   298  		g.Board[move[1]] += delta
   299  	}
   300  
   301  	g.Moves = append(g.Moves, []int8{move[0], move[1]})
   302  	g.setEntered()
   303  	return true
   304  }
   305  
   306  // AddLocalMove adds a move without performing any validation. This is useful when
   307  // adding a move locally while waiting for an EventBoard response from the server.
   308  func (g *Game) AddLocalMove(move []int8) bool {
   309  	return g.addMove(move)
   310  }
   311  
   312  func (g *Game) ExpandMove(move []int8, currentSpace int8, moves [][]int8, local bool) ([][]int8, bool) {
   313  	l := g.LegalMoves(local)
   314  	var hitMoves [][]int8
   315  	for _, m := range l {
   316  		if OpponentCheckers(g.Board[m[1]], g.Turn) == 1 {
   317  			hitMoves = append(hitMoves, m)
   318  		}
   319  	}
   320  	for i := 0; i < 2; i++ {
   321  		var checkMoves [][]int8
   322  		if i == 0 { // Try moves that will hit an opponent's checker first.
   323  			checkMoves = hitMoves
   324  		} else {
   325  			checkMoves = l
   326  		}
   327  		for _, lm := range checkMoves {
   328  			if lm[0] != currentSpace {
   329  				continue
   330  			}
   331  
   332  			newMoves := make([][]int8, len(moves))
   333  			copy(newMoves, moves)
   334  			newMoves = append(newMoves, []int8{lm[0], lm[1]})
   335  
   336  			if lm[1] == move[1] {
   337  				return newMoves, true
   338  			}
   339  
   340  			currentSpace = lm[1]
   341  
   342  			gc := g.Copy(true)
   343  			gc.addMove(lm)
   344  			m, ok := gc.ExpandMove(move, currentSpace, newMoves, local)
   345  			if ok {
   346  				return m, ok
   347  			}
   348  		}
   349  	}
   350  	return nil, false
   351  }
   352  
   353  // AddMoves adds moves to the game state.  Adding a backwards move will remove the equivalent existing move.
   354  func (g *Game) AddMoves(moves [][]int8, local bool) (bool, [][]int8) {
   355  	if g.Player1.Name == "" || g.Player2.Name == "" || g.Winner != 0 {
   356  		return false, nil
   357  	}
   358  
   359  	var addMoves [][]int8
   360  	var undoMoves [][]int8
   361  
   362  	gameCopy := g.Copy(false)
   363  
   364  	validateOffset := 0
   365  VALIDATEMOVES:
   366  	for _, move := range moves {
   367  		l := gameCopy.LegalMoves(local)
   368  		for _, lm := range l {
   369  			if lm[0] == move[0] && lm[1] == move[1] {
   370  				addMoves = append(addMoves, []int8{move[0], move[1]})
   371  				continue VALIDATEMOVES
   372  			}
   373  		}
   374  
   375  		if len(gameCopy.Moves) > 0 {
   376  			i := len(gameCopy.Moves) - 1 - validateOffset
   377  			if i < 0 {
   378  				return false, nil
   379  			}
   380  			gameMove := gameCopy.Moves[i]
   381  			if move[0] == gameMove[1] && move[1] == gameMove[0] {
   382  				undoMoves = append(undoMoves, []int8{gameMove[1], gameMove[0]})
   383  				validateOffset++
   384  				continue VALIDATEMOVES
   385  			}
   386  		}
   387  
   388  		expandedMoves, ok := g.ExpandMove(move, move[0], nil, local)
   389  		if ok {
   390  			for _, expanded := range expandedMoves {
   391  				addMoves = append(addMoves, []int8{expanded[0], expanded[1]})
   392  			}
   393  			continue VALIDATEMOVES
   394  		}
   395  
   396  		return false, nil
   397  	}
   398  
   399  	if len(addMoves) != 0 && len(undoMoves) != 0 {
   400  		return false, nil
   401  	}
   402  
   403  	var checkWin bool
   404  ADDMOVES:
   405  	for _, move := range addMoves {
   406  		l := gameCopy.LegalMoves(local)
   407  		for _, lm := range l {
   408  			if lm[0] == move[0] && lm[1] == move[1] {
   409  				if !gameCopy.addMove(move) {
   410  					return false, nil
   411  				}
   412  
   413  				if move[1] == SpaceHomePlayer || move[1] == SpaceHomeOpponent {
   414  					checkWin = true
   415  				}
   416  				continue ADDMOVES
   417  			}
   418  		}
   419  	}
   420  	for _, move := range undoMoves {
   421  		if len(gameCopy.Moves) > 0 {
   422  			i := len(gameCopy.Moves) - 1
   423  			if i < 0 {
   424  				return false, nil
   425  			}
   426  			gameMove := gameCopy.Moves[i]
   427  			if move[0] == gameMove[1] && move[1] == gameMove[0] {
   428  				copy(gameCopy.Board, gameCopy.boardStates[i])
   429  				gameCopy.Player1.Entered = gameCopy.enteredStates[i][0]
   430  				gameCopy.Player2.Entered = gameCopy.enteredStates[i][1]
   431  				gameCopy.boardStates = gameCopy.boardStates[:i]
   432  				gameCopy.enteredStates = gameCopy.enteredStates[:i]
   433  				gameCopy.Moves = gameCopy.Moves[:i]
   434  				continue
   435  			}
   436  		}
   437  		return false, nil
   438  	}
   439  
   440  	g.Board = append(g.Board[:0], gameCopy.Board...)
   441  	g.Moves = gameCopy.Moves
   442  	g.Player1.Entered, g.Player2.Entered = gameCopy.Player1.Entered, gameCopy.Player2.Entered
   443  	g.boardStates = gameCopy.boardStates
   444  	g.enteredStates = gameCopy.enteredStates
   445  
   446  	if checkWin {
   447  		entered := g.Player1.Entered
   448  		if !local && g.Turn == 2 {
   449  			entered = g.Player2.Entered
   450  		}
   451  
   452  		var foundChecker bool
   453  		if g.Variant != VariantBackgammon && !entered {
   454  			foundChecker = true
   455  		} else {
   456  			for space := 1; space <= 24; space++ {
   457  				if PlayerCheckers(g.Board[space], g.Turn) != 0 {
   458  					foundChecker = true
   459  					break
   460  				}
   461  			}
   462  		}
   463  
   464  		if !foundChecker {
   465  			g.Winner = g.Turn
   466  		}
   467  	}
   468  
   469  	if len(addMoves) > 0 {
   470  		return true, addMoves
   471  	} else {
   472  		return true, undoMoves
   473  	}
   474  }
   475  
   476  func (g *Game) DiceRolls() []int8 {
   477  	rolls := []int8{
   478  		g.Roll1,
   479  		g.Roll2,
   480  	}
   481  	if g.Variant == VariantTabula {
   482  		rolls = append(rolls, g.Roll3)
   483  	} else if g.Roll1 == g.Roll2 {
   484  		rolls = append(rolls, g.Roll1, g.Roll2)
   485  	}
   486  
   487  	useDiceRoll := func(from, to int8) bool {
   488  		if to == SpaceHomePlayer || to == SpaceHomeOpponent {
   489  			needRoll := from
   490  			if to == SpaceHomeOpponent || g.Variant == VariantTabula {
   491  				needRoll = 25 - from
   492  			}
   493  			for i, roll := range rolls {
   494  				if roll == needRoll {
   495  					rolls = append(rolls[:i], rolls[i+1:]...)
   496  					return true
   497  				}
   498  			}
   499  			for i, roll := range rolls {
   500  				if roll > needRoll {
   501  					rolls = append(rolls[:i], rolls[i+1:]...)
   502  					return true
   503  				}
   504  			}
   505  			return false
   506  		}
   507  
   508  		diff := SpaceDiff(from, to, g.Variant)
   509  		for i, roll := range rolls {
   510  			if roll == diff {
   511  				rolls = append(rolls[:i], rolls[i+1:]...)
   512  				return true
   513  			}
   514  		}
   515  		return false
   516  	}
   517  
   518  	for _, move := range g.Moves {
   519  		if !useDiceRoll(move[0], move[1]) {
   520  			return nil
   521  		}
   522  	}
   523  
   524  	return rolls
   525  }
   526  
   527  func (g *Game) HaveDiceRoll(from int8, to int8) int8 {
   528  	if g.Variant == VariantTabula && to > 12 && to < 25 && ((g.Turn == 1 && !g.Player1.Entered) || (g.Turn == 2 && !g.Player2.Entered)) {
   529  		return 0
   530  	} else if (to == SpaceHomePlayer || to == SpaceHomeOpponent) && !g.MayBearOff(g.Turn, false) {
   531  		return 0
   532  	}
   533  	diff := SpaceDiff(from, to, g.Variant)
   534  	if diff == 0 {
   535  		return 0
   536  	}
   537  	var c int8
   538  	for _, roll := range g.DiceRolls() {
   539  		if roll == diff {
   540  			c++
   541  		}
   542  	}
   543  	return c
   544  }
   545  
   546  func (g *Game) HaveBearOffDiceRoll(diff int8) int8 {
   547  	if diff == 0 {
   548  		return 0
   549  	}
   550  	var c int8
   551  	for _, roll := range g.DiceRolls() {
   552  		if roll == diff || (roll > diff && g.Variant == VariantBackgammon) {
   553  			c++
   554  		}
   555  	}
   556  	return c
   557  }
   558  
   559  func (g *Game) LegalMoves(local bool) [][]int8 {
   560  	if g.Turn == 0 {
   561  		return nil
   562  	}
   563  	b, ok := g.TabulaBoard()
   564  	if !ok {
   565  		return nil
   566  	}
   567  	barSpace := SpaceBarPlayer
   568  	if g.Turn == 2 {
   569  		barSpace = SpaceBarOpponent
   570  	}
   571  	onBar := g.Board[barSpace] != 0
   572  	available, _ := b.Available(g.Turn)
   573  	var moves [][]int8
   574  	for i := range available {
   575  		for j := range available[i] {
   576  			if available[i][j][0] == 0 && available[i][j][1] == 0 {
   577  				break
   578  			}
   579  			if (!onBar || (onBar && available[i][j][0] == barSpace)) && PlayerCheckers(g.Board[available[i][j][0]], g.Turn) != 0 {
   580  				var found bool
   581  				for _, m := range moves {
   582  					if m[0] == available[i][j][0] && m[1] == available[i][j][1] {
   583  						found = true
   584  						break
   585  					}
   586  				}
   587  				if !found {
   588  					moves = append(moves, []int8{available[i][j][0], available[i][j][1]})
   589  				}
   590  			}
   591  		}
   592  	}
   593  	return moves
   594  }
   595  
   596  // MayBearOff returns whether the provided player may bear checkers off of the board.
   597  func (g *Game) MayBearOff(player int8, local bool) bool {
   598  	if PlayerCheckers(g.Board[SpaceBarPlayer], player) > 0 || PlayerCheckers(g.Board[SpaceBarOpponent], player) > 0 {
   599  		return false
   600  	} else if (player == 1 && !g.Player1.Entered) || (player == 2 && !g.Player2.Entered) {
   601  		return false
   602  	} else if g.Variant == VariantTabula {
   603  		return g.SecondHalf(player, local)
   604  	}
   605  
   606  	homeStart, homeEnd := int8(1), int8(6)
   607  	if !local {
   608  		homeStart, homeEnd = HomeRange(player, g.Variant)
   609  		homeStart, homeEnd = minInt(homeStart, homeEnd), maxInt(homeStart, homeEnd)
   610  	}
   611  	for i := int8(1); i <= 24; i++ {
   612  		if (i < homeStart || i > homeEnd) && PlayerCheckers(g.Board[i], player) > 0 {
   613  			return false
   614  		}
   615  	}
   616  	return true
   617  }
   618  
   619  func (g *Game) RenderSpace(player int8, space int8, spaceValue int8, legalMoves [][]int8) []byte {
   620  	var playerColor = "x"
   621  	var opponentColor = "o"
   622  	if player == 2 {
   623  		playerColor = "o"
   624  		opponentColor = "x"
   625  	}
   626  
   627  	var pieceColor string
   628  	value := g.Board[space]
   629  	if space == SpaceBarPlayer {
   630  		pieceColor = playerColor
   631  	} else if space == SpaceBarOpponent {
   632  		pieceColor = opponentColor
   633  	} else {
   634  		if value < 0 {
   635  			pieceColor = "o"
   636  		} else if value > 0 {
   637  			pieceColor = "x"
   638  		} else {
   639  			pieceColor = playerColor
   640  		}
   641  	}
   642  
   643  	abs := value
   644  	if value < 0 {
   645  		abs = value * -1
   646  	}
   647  
   648  	top := space > 12
   649  	if player == 2 {
   650  		top = !top
   651  	}
   652  
   653  	var firstDigit int8 = 4
   654  	var secondDigit int8 = 5
   655  	if !top {
   656  		firstDigit = 5
   657  		secondDigit = 4
   658  	}
   659  
   660  	var firstNumeral string
   661  	var secondNumeral string
   662  	if abs > 5 {
   663  		if abs > 9 {
   664  			firstNumeral = "1"
   665  		} else {
   666  			firstNumeral = strconv.Itoa(int(abs))
   667  		}
   668  		if abs > 9 {
   669  			secondNumeral = strconv.Itoa(int(abs) - 10)
   670  		}
   671  
   672  		if spaceValue == firstDigit && (!top || abs > 9) {
   673  			pieceColor = firstNumeral
   674  		} else if spaceValue == secondDigit && abs > 9 {
   675  			pieceColor = secondNumeral
   676  		} else if top && spaceValue == secondDigit {
   677  			pieceColor = firstNumeral
   678  		}
   679  	}
   680  
   681  	if abs > 5 {
   682  		abs = 5
   683  	}
   684  
   685  	var r []byte
   686  	if abs > 0 && spaceValue <= abs {
   687  		r = []byte(pieceColor)
   688  	} else {
   689  		r = []byte(" ")
   690  	}
   691  	return append(append([]byte(" "), r...), ' ')
   692  }
   693  
   694  func (g *Game) BoardState(player int8, local bool) []byte {
   695  	var t bytes.Buffer
   696  
   697  	playerRating := "0"
   698  	opponentRating := "0"
   699  
   700  	var white bool
   701  	if player == 2 {
   702  		white = true
   703  	}
   704  
   705  	var opponentName = g.Player2.Name
   706  	var playerName = g.Player1.Name
   707  	if playerName == "" {
   708  		playerName = "Waiting..."
   709  	}
   710  	if opponentName == "" {
   711  		opponentName = "Waiting..."
   712  	}
   713  	if white {
   714  		playerName, opponentName = opponentName, playerName
   715  	}
   716  
   717  	var playerColor = "x"
   718  	var opponentColor = "o"
   719  	playerRoll := g.Roll1
   720  	opponentRoll := g.Roll2
   721  	if white {
   722  		playerColor = "o"
   723  		opponentColor = "x"
   724  		playerRoll = g.Roll2
   725  		opponentRoll = g.Roll1
   726  	}
   727  
   728  	if white {
   729  		t.Write(boardTopWhite)
   730  	} else {
   731  		t.Write(boardTopBlack)
   732  	}
   733  	t.WriteString(" ")
   734  	t.WriteByte('\n')
   735  
   736  	legalMoves := g.LegalMoves(local)
   737  	space := func(row int8, col int8) []byte {
   738  		var spaceValue int8 = row + 1
   739  		if row > 5 {
   740  			spaceValue = 5 - (row - 6)
   741  		}
   742  
   743  		if col == -1 {
   744  			if row <= 4 {
   745  				return g.RenderSpace(player, SpaceBarOpponent, spaceValue, legalMoves)
   746  			}
   747  			return g.RenderSpace(player, SpaceBarPlayer, spaceValue, legalMoves)
   748  		}
   749  
   750  		var space int8
   751  		if white {
   752  			space = 24 - col
   753  			if row > 5 {
   754  				space = 1 + col
   755  			}
   756  		} else {
   757  			space = 13 + col
   758  			if row > 5 {
   759  				space = 12 - col
   760  			}
   761  		}
   762  
   763  		if row == 5 {
   764  			return []byte("   ")
   765  		}
   766  
   767  		return g.RenderSpace(player, space, spaceValue, legalMoves)
   768  	}
   769  
   770  	const verticalBar rune = '│'
   771  	for i := int8(0); i < 11; i++ {
   772  		t.WriteRune(verticalBar)
   773  		t.Write([]byte(""))
   774  		for j := int8(0); j < 12; j++ {
   775  			t.Write(space(i, j))
   776  
   777  			if j == 5 {
   778  				t.WriteRune(verticalBar)
   779  				t.Write(space(i, -1))
   780  				t.WriteRune(verticalBar)
   781  			}
   782  		}
   783  
   784  		t.Write([]byte("" + string(verticalBar) + "  "))
   785  
   786  		if i == 0 {
   787  			t.Write([]byte(opponentColor + " " + opponentName + " (" + opponentRating + ")"))
   788  			if g.Board[SpaceHomeOpponent] != 0 {
   789  				v := g.Board[SpaceHomeOpponent]
   790  				if v < 0 {
   791  					v *= -1
   792  				}
   793  				t.Write([]byte(fmt.Sprintf("  %d off", v)))
   794  			}
   795  		} else if i == 2 {
   796  			if g.Turn == 0 {
   797  				if g.Player1.Name != "" && g.Player2.Name != "" {
   798  					if opponentRoll != 0 {
   799  						t.Write([]byte(fmt.Sprintf("  %d", opponentRoll)))
   800  					} else {
   801  						t.Write([]byte("  -"))
   802  					}
   803  				}
   804  			} else if g.Turn != player {
   805  				if g.Roll1 > 0 {
   806  					t.Write([]byte(fmt.Sprintf("  %d  %d  ", g.Roll1, g.Roll2)))
   807  					if g.Roll3 != 0 {
   808  						t.Write([]byte(fmt.Sprintf("%d  ", g.Roll3)))
   809  					}
   810  				} else if opponentName != "" {
   811  					t.Write([]byte("  -  -  "))
   812  				}
   813  			}
   814  		} else if i == 8 {
   815  			if g.Turn == 0 {
   816  				if g.Player1.Name != "" && g.Player2.Name != "" {
   817  					if playerRoll != 0 {
   818  						t.Write([]byte(fmt.Sprintf("  %d", playerRoll)))
   819  					} else {
   820  						t.Write([]byte("  -"))
   821  					}
   822  				}
   823  			} else if g.Turn == player {
   824  				if g.Roll1 > 0 {
   825  					t.Write([]byte(fmt.Sprintf("  %d  %d  ", g.Roll1, g.Roll2)))
   826  					if g.Roll3 != 0 {
   827  						t.Write([]byte(fmt.Sprintf("%d  ", g.Roll3)))
   828  					}
   829  				} else if playerName != "" {
   830  					t.Write([]byte("  -  -  "))
   831  				}
   832  			}
   833  		} else if i == 10 {
   834  			t.Write([]byte(playerColor + " " + playerName + " (" + playerRating + ")"))
   835  			if g.Board[SpaceHomePlayer] != 0 {
   836  				v := g.Board[SpaceHomePlayer]
   837  				if v < 0 {
   838  					v *= -1
   839  				}
   840  				t.Write([]byte(fmt.Sprintf("  %d off", v)))
   841  			}
   842  		}
   843  
   844  		t.Write([]byte(" "))
   845  		t.WriteByte('\n')
   846  	}
   847  
   848  	if white {
   849  		t.Write(boardBottomWhite)
   850  	} else {
   851  		t.Write(boardBottomBlack)
   852  	}
   853  	t.WriteString("                 \n")
   854  
   855  	return t.Bytes()
   856  }
   857  
   858  func SpaceDiff(from int8, to int8, variant int8) int8 {
   859  	switch {
   860  	case from < 0 || from > 27 || to < 0 || to > 27:
   861  		return 0
   862  	case to == SpaceBarPlayer || to == SpaceBarOpponent:
   863  		return 0
   864  	case (from == SpaceBarPlayer || from == SpaceBarOpponent) && (to == SpaceBarPlayer || to == SpaceBarOpponent || to == SpaceHomePlayer || to == SpaceHomeOpponent):
   865  		return 0
   866  	case to == SpaceHomePlayer:
   867  		if variant == VariantTabula {
   868  			return 25 - from
   869  		}
   870  		return from
   871  	case to == SpaceHomeOpponent:
   872  		return 25 - from
   873  	case from == SpaceHomePlayer || from == SpaceHomeOpponent:
   874  		switch variant {
   875  		case VariantAceyDeucey:
   876  			if from == SpaceHomePlayer {
   877  				return 25 - to
   878  			} else {
   879  				return to
   880  			}
   881  		case VariantTabula:
   882  			return to
   883  		}
   884  		return 0
   885  	case from == SpaceBarPlayer:
   886  		if variant == VariantTabula {
   887  			return to
   888  		}
   889  		return 25 - to
   890  	case from == SpaceBarOpponent:
   891  		return to
   892  	default:
   893  		diff := to - from
   894  		if diff < 0 {
   895  			return diff * -1
   896  		}
   897  		return diff
   898  	}
   899  }
   900  
   901  func IterateSpaces(from int8, to int8, variant int8, f func(space int8, spaceCount int8)) {
   902  	if from == to || from < 0 || from > 25 || to < 0 || to > 25 {
   903  		return
   904  	} else if variant == VariantBackgammon {
   905  		if from == 0 {
   906  			from = 1
   907  		} else if from == 25 {
   908  			from = 24
   909  		}
   910  	}
   911  	var i int8 = 1
   912  	if to > from {
   913  		for space := from; space <= to; space++ {
   914  			f(space, i)
   915  			i++
   916  		}
   917  	} else {
   918  		for space := from; space >= to; space-- {
   919  			f(space, i)
   920  			i++
   921  		}
   922  	}
   923  }
   924  
   925  func PlayerCheckers(checkers int8, player int8) int8 {
   926  	if player == 1 {
   927  		if checkers > 0 {
   928  			return checkers
   929  		}
   930  		return 0
   931  	} else {
   932  		if checkers < 0 {
   933  			return checkers * -1
   934  		}
   935  		return 0
   936  	}
   937  }
   938  
   939  func OpponentCheckers(checkers int8, player int8) int8 {
   940  	if player == 2 {
   941  		if checkers > 0 {
   942  			return checkers
   943  		}
   944  		return 0
   945  	} else {
   946  		if checkers < 0 {
   947  			return checkers * -1
   948  		}
   949  		return 0
   950  	}
   951  }
   952  
   953  func FlipSpace(space int8, player int8, variant int8) int8 {
   954  	if player == 1 {
   955  		return space
   956  	}
   957  	if space < 1 || space > 24 {
   958  		switch space {
   959  		case SpaceHomePlayer:
   960  			return SpaceHomeOpponent
   961  		case SpaceHomeOpponent:
   962  			return SpaceHomePlayer
   963  		case SpaceBarPlayer:
   964  			return SpaceBarOpponent
   965  		case SpaceBarOpponent:
   966  			return SpaceBarPlayer
   967  		default:
   968  			return -1
   969  		}
   970  	}
   971  	if variant == VariantTabula {
   972  		return space
   973  	}
   974  	return 24 - space + 1
   975  }
   976  
   977  func FlipMoves(moves [][]int8, player int8, variant int8) [][]int8 {
   978  	m := make([][]int8, len(moves))
   979  	for i := range moves {
   980  		m[i] = []int8{FlipSpace(moves[i][0], player, variant), FlipSpace(moves[i][1], player, variant)}
   981  	}
   982  	return m
   983  }
   984  
   985  func FormatSpace(space int8) []byte {
   986  	if space >= 1 && space <= 24 {
   987  		return []byte(strconv.Itoa(int(space)))
   988  	} else if space == SpaceBarPlayer || space == SpaceBarOpponent {
   989  		return []byte("bar")
   990  	} else if space == SpaceHomePlayer || space == SpaceHomeOpponent {
   991  		return []byte("off")
   992  	}
   993  	return []byte("?")
   994  }
   995  
   996  func FormatMoves(moves [][]int8) []byte {
   997  	if len(moves) == 0 {
   998  		return []byte("none")
   999  	}
  1000  
  1001  	var out bytes.Buffer
  1002  	for i := range moves {
  1003  		if i != 0 {
  1004  			out.WriteByte(' ')
  1005  		}
  1006  		out.Write([]byte(fmt.Sprintf("%s/%s", FormatSpace(moves[i][0]), FormatSpace(moves[i][1]))))
  1007  	}
  1008  	return out.Bytes()
  1009  }
  1010  
  1011  func FormatAndFlipMoves(moves [][]int8, player int8, variant int8) []byte {
  1012  	return FormatMoves(FlipMoves(moves, player, variant))
  1013  }
  1014  
  1015  func ValidSpace(space int8) bool {
  1016  	return space >= 0 && space <= 27
  1017  }
  1018  
  1019  func (g *Game) TabulaBoard() (tabula.Board, bool) {
  1020  	var roll1, roll2, roll3, roll4 int8
  1021  	roll1, roll2 = int8(g.Roll1), int8(g.Roll2)
  1022  	if g.Variant == VariantTabula {
  1023  		roll3 = int8(g.Roll3)
  1024  	} else if roll1 == roll2 {
  1025  		roll3, roll4 = int8(g.Roll1), int8(g.Roll2)
  1026  	}
  1027  	entered1, entered2 := int8(1), int8(1)
  1028  	if g.Variant != VariantBackgammon {
  1029  		if !g.Player1.Entered {
  1030  			entered1 = 0
  1031  		}
  1032  		if !g.Player2.Entered {
  1033  			entered2 = 0
  1034  		}
  1035  	}
  1036  	b := g.Board
  1037  	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}
  1038  	for _, move := range g.Moves {
  1039  		diff := SpaceDiff(move[0], move[1], g.Variant)
  1040  		if diff == 0 {
  1041  			return tabula.Board{}, false
  1042  		}
  1043  		if tb[tabula.SpaceRoll1] == diff {
  1044  			tb[tabula.SpaceRoll1] = 0
  1045  			continue
  1046  		} else if tb[tabula.SpaceRoll2] == diff {
  1047  			tb[tabula.SpaceRoll2] = 0
  1048  			continue
  1049  		} else if tb[tabula.SpaceRoll3] == diff {
  1050  			tb[tabula.SpaceRoll3] = 0
  1051  			continue
  1052  		} else if tb[tabula.SpaceRoll4] == diff {
  1053  			tb[tabula.SpaceRoll4] = 0
  1054  			continue
  1055  		}
  1056  		var highest = tabula.SpaceRoll1
  1057  		if tb[tabula.SpaceRoll2] > tb[tabula.SpaceRoll1] {
  1058  			highest = tabula.SpaceRoll2
  1059  		}
  1060  		if tb[tabula.SpaceRoll3] > tb[highest] {
  1061  			highest = tabula.SpaceRoll3
  1062  		}
  1063  		if tb[tabula.SpaceRoll4] > tb[highest] {
  1064  			highest = tabula.SpaceRoll4
  1065  		}
  1066  		if tb[highest] < diff {
  1067  			return tabula.Board{}, false
  1068  		}
  1069  		tb[highest] = 0
  1070  	}
  1071  	return tb, true
  1072  }
  1073  

View as plain text