Terminal-based online backgammon client (FIBS)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
6.1 KiB

4 months ago
package main
import (
"strconv"
"code.rocketnine.space/tslocum/cview"
"code.rocketnine.space/tslocum/fibs"
"github.com/gdamore/tcell/v2"
4 months ago
)
const SpaceUnknown = -1
type GameBoard struct {
Board *fibs.Board
4 months ago
*cview.TextView
selected [2]int
dragFromX, dragFromY int
4 months ago
}
func NewGameBoard(client *fibs.Client) *GameBoard {
b := &GameBoard{
Board: client.Board,
TextView: cview.NewTextView(),
4 months ago
}
b.TextView.SetRegions(true)
b.TextView.SetDynamicColors(true)
b.TextView.SetToggleHighlights(true)
b.TextView.SetHighlightedFunc(b.handleHighlight)
4 months ago
b.Update()
return b
4 months ago
}
func (b *GameBoard) Update() {
b.TextView.SetBytes(b.Board.Render())
4 months ago
}
func (b *GameBoard) resetSelection() {
b.selected[0] = 0
b.selected[1] = 0
}
func (b *GameBoard) handleHighlight(added, removed, remaining []string) {
defer b.Update()
v := b.Board.GetIntState()
if len(added) > 0 && len(remaining) > 0 {
if added[0] == "space-0" || added[0] == "space-25" {
// Deselect
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(added[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
return
}
from, err := strconv.Atoi(remaining[0][6:])
if err != nil {
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(remaining[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
return
}
to, err := strconv.Atoi(added[0][6:])
if err != nil {
if added[0] == "space-off" {
to = b.Board.PlayerHomeSpace()
} else {
return
}
}
var spaces int
calcFrom := from
if to > 0 && to < 25 {
if to >= calcFrom {
spaces = to - calcFrom
} else {
spaces = calcFrom - to
}
}
var mid = SpaceUnknown
if (v[fibs.StatePlayerDice1] != v[fibs.StatePlayerDice2] && spaces*b.selected[1] == v[fibs.StatePlayerDice1]+v[fibs.StatePlayerDice2]) ||
(v[fibs.StatePlayerDice1] == v[fibs.StatePlayerDice2] && spaces*b.selected[1] == (v[fibs.StatePlayerDice1]+v[fibs.StatePlayerDice2])*2) {
// Prefer any move that will bar opponent
for i := 0; i < 2; i++ {
dice := v[fibs.StatePlayerDice1]
if i == 2 {
dice = v[fibs.StatePlayerDice2]
}
index := calcFrom + (dice * v[fibs.StateDirection])
if index == to {
continue
}
if (v[fibs.StateBoardSpace0+index] == -1 && v[fibs.StatePlayerColor] > 0) ||
(v[fibs.StateBoardSpace0+index] == 1 && v[fibs.StatePlayerColor] < 0) {
mid = index
break
}
}
if mid == SpaceUnknown {
// Send any valid move
for i := 0; i < 2; i++ {
dice := v[fibs.StatePlayerDice1]
if i == 2 {
dice = v[fibs.StatePlayerDice2]
}
index := calcFrom + (dice * v[fibs.StateDirection])
if index == to {
continue
}
if (v[fibs.StateBoardSpace0+index] >= 0 && v[fibs.StatePlayerColor] > 0) ||
(v[fibs.StateBoardSpace0+index] <= 0 && v[fibs.StatePlayerColor] < 0) {
mid = index
break
}
}
}
}
for i := 0; i < b.selected[1]; i++ {
if mid < 0 {
b.Board.AddPreMove(from, to)
} else {
b.Board.AddPreMove(from, mid)
b.Board.AddPreMove(mid, to)
}
}
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(added[0])
b.TextView.Highlight(remaining[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
} else if len(added) > 0 {
if added[0] == "space-off" {
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(added[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
return
} else if (added[0] == "space-0" || added[0] == "space-25") && v[fibs.StatePlayerBar] == 0 {
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(added[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
return
}
index, err := strconv.Atoi(added[0][6:])
if err == nil {
abs := v[fibs.StateBoardSpace0+index]
if abs < 0 {
abs *= -1
}
if added[0] == "space-0" || added[0] == "space-25" {
abs = v[fibs.StatePlayerBar]
}
if b.selected[1] >= abs && false { // TODO or has premove piece in space
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(added[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
return
}
b.selected[0] = index
b.selected[1]++
}
} else if len(removed) > 0 {
index, err := strconv.Atoi(removed[0][6:])
if err == nil {
abs := v[fibs.StateBoardSpace0+index]
if abs < 0 {
abs *= -1
}
if removed[0] == "space-0" || removed[0] == "space-25" {
abs = v[fibs.StatePlayerBar]
}
if b.selected[1] < abs {
b.selected[0] = index
b.selected[1]++
}
b.TextView.SetHighlightedFunc(nil)
b.TextView.Highlight(removed[0])
b.TextView.SetHighlightedFunc(b.handleHighlight)
}
}
}
// MouseHandler returns the mouse handler for this primitive.
func (b *GameBoard) MouseHandler() func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
return b.WrapMouseHandler(func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.Primitive) {
x, y := event.Position()
if !b.InRect(x, y) {
return false, nil
}
switch action {
case cview.MouseLeftDown:
b.dragFromX, b.dragFromY = x, y
case cview.MouseLeftUp:
if b.dragFromX != x || b.dragFromY != y {
app.QueueUpdateDraw(func() {
// Simulate click event at start of drag
fromEvent := tcell.NewEventMouse(b.dragFromX, b.dragFromY, tcell.ButtonPrimary, event.Modifiers())
b.TextView.MouseHandler()(cview.MouseLeftClick, fromEvent, setFocus)
consumed, _ = b.TextView.MouseHandler()(cview.MouseLeftClick, event, setFocus)
if consumed {
// Succeeded
return
}
// Failed, undo
b.TextView.MouseHandler()(cview.MouseLeftClick, fromEvent, setFocus)
})
}
case cview.MouseRightClick:
b.TextView.SetHighlightedFunc(nil)
h := b.GetHighlights()
for i := range h {
b.Highlight(h[i])
}
b.TextView.SetHighlightedFunc(b.handleHighlight)
b.resetSelection()
b.Update()
consumed = true
return
}
return b.TextView.MouseHandler()(action, event, setFocus)
})
}