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
34 Board []int8
35 Turn int8
36
37 Roll1 int8
38 Roll2 int8
39 Roll3 int8
40
41 Moves [][]int8
42 Winner int8
43
44 Points int8
45 DoubleValue int8
46 DoublePlayer int8
47 DoubleOffered bool
48
49 Reroll bool
50
51 partialTurn int8
52 partialTime time.Time
53 partialHandled bool
54
55 boardStates [][]int8
56 enteredStates [][2]bool
57
58
59 Acey bool
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
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 {
289 g.Board[move[1]] = delta
290
291
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
307
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 {
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
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
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