Extract FIBS-related code as library
parent
f029843bf9
commit
7381027a05
8
LICENSE
8
LICENSE
|
@ -1,7 +1,7 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
|
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
84
app.go
84
app.go
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"code.rocketnine.space/tslocum/cview"
|
||||
"code.rocketnine.space/tslocum/fibs"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
|
@ -19,7 +20,7 @@ var (
|
|||
uiGrid *cview.Grid
|
||||
userList *cview.List
|
||||
userListGrid *cview.Grid
|
||||
board *Board
|
||||
board *GameBoard
|
||||
actionBuffer *cview.TextView
|
||||
gameBuffer *cview.TextView
|
||||
statusBuffer *cview.TextView
|
||||
|
@ -75,14 +76,14 @@ func updateClock() {
|
|||
}
|
||||
}
|
||||
|
||||
func logIn(c *Client) {
|
||||
func logIn(c *fibs.Client) {
|
||||
app.SetRoot(uiGrid, true)
|
||||
app.SetFocus(inputField)
|
||||
|
||||
go c.connect()
|
||||
go c.Connect()
|
||||
}
|
||||
|
||||
func setScreen(c *Client, screen int) {
|
||||
func setScreen(c *fibs.Client, screen int) {
|
||||
viewScreen = screen
|
||||
if viewScreen == ScreenLobby {
|
||||
updateUserList(c)
|
||||
|
@ -95,38 +96,37 @@ func setScreen(c *Client, screen int) {
|
|||
buildLayout(c)
|
||||
}
|
||||
|
||||
func updateUserList(c *Client) {
|
||||
var infos []*WhoInfo
|
||||
for _, whoInfo := range c.who {
|
||||
infos = append(infos, whoInfo)
|
||||
}
|
||||
func updateUserList(c *fibs.Client) {
|
||||
infos := c.GetAllWhoInfo()
|
||||
sort.Slice(infos, func(i, j int) bool {
|
||||
if (infos[i].opponent == "") != (infos[j].opponent == "") {
|
||||
return infos[i].opponent == ""
|
||||
if (infos[i].Opponent == "") != (infos[j].Opponent == "") {
|
||||
return infos[i].Opponent == ""
|
||||
}
|
||||
|
||||
if (infos[i].watching == "") != (infos[j].watching == "") {
|
||||
return infos[i].watching == ""
|
||||
if (infos[i].Watching == "") != (infos[j].Watching == "") {
|
||||
return infos[i].Watching == ""
|
||||
}
|
||||
|
||||
if infos[i].ready != infos[j].ready {
|
||||
return infos[i].ready
|
||||
if infos[i].Ready != infos[j].Ready {
|
||||
return infos[i].Ready
|
||||
}
|
||||
|
||||
if infos[i].rating != infos[j].rating {
|
||||
return infos[i].rating < infos[j].rating
|
||||
if infos[i].Rating != infos[j].Rating {
|
||||
return infos[i].Rating < infos[j].Rating
|
||||
}
|
||||
|
||||
return strings.ToLower(infos[i].username) < strings.ToLower(infos[j].username)
|
||||
return strings.ToLower(infos[i].Username) < strings.ToLower(infos[j].Username)
|
||||
})
|
||||
|
||||
userList.Clear()
|
||||
for _, whoInfo := range infos {
|
||||
userList.AddItem(whoInfo.listItem)
|
||||
//userList.AddItem(whoInfo.listItem)
|
||||
// TODO add list item
|
||||
_ = whoInfo
|
||||
}
|
||||
}
|
||||
|
||||
func buildLayout(c *Client) {
|
||||
func buildLayout(c *fibs.Client) {
|
||||
uiGrid.Clear()
|
||||
|
||||
var currentScreen cview.Primitive
|
||||
|
@ -158,14 +158,14 @@ func buildLayout(c *Client) {
|
|||
uiGrid.AddItem(inputField, 3, 0, 1, 3, 0, 0, true)
|
||||
}
|
||||
|
||||
func RunApp(c *Client) error {
|
||||
func RunApp(c *fibs.Client, b *GameBoard) error {
|
||||
app = cview.NewApplication()
|
||||
app.EnableMouse(true)
|
||||
|
||||
var focusFunc func(p cview.Primitive) bool
|
||||
|
||||
focusFunc = func(p cview.Primitive) bool {
|
||||
if !c.loggedin {
|
||||
if !c.LoggedIn() {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ func RunApp(c *Client) error {
|
|||
text = "kibitz " + text
|
||||
}
|
||||
|
||||
c.out <- []byte(text)
|
||||
c.Out <- []byte(text)
|
||||
inputField.SetText("")
|
||||
})
|
||||
inputField.SetFieldBackgroundColor(cview.Styles.PrimitiveBackgroundColor)
|
||||
|
@ -245,14 +245,14 @@ func RunApp(c *Client) error {
|
|||
form.AddFormItem(passwordField)
|
||||
|
||||
connectFunc := func() {
|
||||
c.username = usernameField.GetText()
|
||||
c.password = passwordField.GetText()
|
||||
c.Username = usernameField.GetText()
|
||||
c.Password = passwordField.GetText()
|
||||
|
||||
logIn(c)
|
||||
}
|
||||
|
||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if !c.loggedin {
|
||||
if !c.LoggedIn() {
|
||||
if event.Key() == tcell.KeyEnter {
|
||||
formIndex, buttonIndex := form.GetFocusedItemIndex()
|
||||
if formIndex > 0 || buttonIndex == 0 {
|
||||
|
@ -310,6 +310,9 @@ func RunApp(c *Client) error {
|
|||
statusWriter = &bufferWriter{Buffer: statusBuffer}
|
||||
gameWriter = &bufferWriter{Buffer: gameBuffer}
|
||||
|
||||
fibs.StatusWriter = statusWriter
|
||||
fibs.GameWriter = gameWriter
|
||||
|
||||
userList = cview.NewList()
|
||||
userList.ShowSecondaryText(false)
|
||||
userList.SetHighlightFullLine(true)
|
||||
|
@ -345,12 +348,14 @@ func RunApp(c *Client) error {
|
|||
// TODO refactor
|
||||
buildLayout(c)
|
||||
defer func() {
|
||||
if c.username != "" && c.password != "" {
|
||||
if c.Username != "" && c.Password != "" {
|
||||
app.SetRoot(uiGrid, true)
|
||||
app.SetFocus(inputField)
|
||||
}
|
||||
}()
|
||||
|
||||
go HandleEvents(c, b)
|
||||
|
||||
setScreen(c, ScreenGame)
|
||||
|
||||
lg("+---------------------------------------------------+")
|
||||
|
@ -362,7 +367,7 @@ func RunApp(c *Client) error {
|
|||
lg("| |")
|
||||
lg("+---------------------------------------------------+")
|
||||
|
||||
if c.username == "" || c.password == "" {
|
||||
if c.Username == "" || c.Password == "" {
|
||||
app.SetRoot(f2, true)
|
||||
app.SetFocus(form)
|
||||
} else {
|
||||
|
@ -373,3 +378,26 @@ func RunApp(c *Client) error {
|
|||
|
||||
return app.Run()
|
||||
}
|
||||
|
||||
func HandleEvents(c *fibs.Client, b *GameBoard) {
|
||||
for e := range c.Event {
|
||||
switch event := e.(type) {
|
||||
case *fibs.EventBoardState:
|
||||
//b.SetState(event.S, event.V)
|
||||
l("STATE")
|
||||
b.Update()
|
||||
app.Draw()
|
||||
case *fibs.EventMove:
|
||||
//b.movePiece(event.From, event.To)
|
||||
l("MOVE")
|
||||
b.Update()
|
||||
app.Draw()
|
||||
case *fibs.EventDraw:
|
||||
//b.ProcessState()
|
||||
l("DRAW")
|
||||
_ = event
|
||||
b.Update()
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
799
board.go
799
board.go
|
@ -1,761 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.rocketnine.space/tslocum/cview"
|
||||
"code.rocketnine.space/tslocum/fibs"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// TODO Add PlayerName, etc
|
||||
const SpaceUnknown = -1
|
||||
|
||||
const (
|
||||
StateLength = iota
|
||||
StatePlayerScore
|
||||
StateOpponentScore
|
||||
StateBoardSpace0
|
||||
)
|
||||
|
||||
const (
|
||||
StateTurn = 29 + iota
|
||||
StatePlayerDice1
|
||||
StatePlayerDice2
|
||||
StateOpponentDice1
|
||||
StateOpponentDice2
|
||||
StateDoublingValue
|
||||
StatePlayerMayDouble
|
||||
StateOpponentMayDouble
|
||||
StateWasDoubled
|
||||
StatePlayerColor
|
||||
StateDirection
|
||||
StateObsoleteHome
|
||||
StateObsoleteBar
|
||||
StatePlayerHome
|
||||
StateOpponentHome
|
||||
StatePlayerBar
|
||||
StateOpponentBar
|
||||
StateMovablePieces
|
||||
StateObsoletePlayerForced
|
||||
StateObsoleteOpponentForced
|
||||
StateRedoubles
|
||||
)
|
||||
|
||||
const (
|
||||
SpaceUnknown = -1
|
||||
)
|
||||
|
||||
var boardTopWhite = []byte("+13-14-15-16-17-18-+---+19-20-21-22-23-24-+")
|
||||
var boardBottomWhite = []byte("+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+")
|
||||
|
||||
var boardTopBlack = []byte("+-1--2--3--4--5--6-+---+-7--8--9-10-11-12-+")
|
||||
var boardBottomBlack = []byte("+24-23-22-21-20-19-+---+18-17-16-15-14-13-+")
|
||||
|
||||
type Board struct {
|
||||
type GameBoard struct {
|
||||
Board *fibs.Board
|
||||
*cview.TextView
|
||||
|
||||
client *Client
|
||||
|
||||
state string
|
||||
|
||||
s []string
|
||||
v []int
|
||||
|
||||
moves [][2]int
|
||||
movesColor int
|
||||
|
||||
validMoves map[int][]int
|
||||
|
||||
from map[int]int
|
||||
to map[int]int
|
||||
|
||||
playerDice [2]int
|
||||
opponentDice [2]int
|
||||
|
||||
selected [2]int
|
||||
|
||||
premove [][2]int
|
||||
premovefrom map[int]int
|
||||
premoveto map[int]int
|
||||
|
||||
dragFromX int
|
||||
dragFromY int
|
||||
dragFromX, dragFromY int
|
||||
}
|
||||
|
||||
func NewBoard(client *Client) *Board {
|
||||
b := &Board{
|
||||
func NewGameBoard(client *fibs.Client) *GameBoard {
|
||||
b := &GameBoard{
|
||||
Board: client.Board,
|
||||
TextView: cview.NewTextView(),
|
||||
client: client,
|
||||
}
|
||||
b.TextView.SetRegions(true)
|
||||
b.TextView.SetDynamicColors(true)
|
||||
b.TextView.SetToggleHighlights(true)
|
||||
b.TextView.SetHighlightedFunc(b.handleHighlight)
|
||||
|
||||
tv := b.TextView
|
||||
tv.SetRegions(true)
|
||||
tv.SetDynamicColors(true)
|
||||
tv.SetToggleHighlights(true)
|
||||
tv.SetHighlightedFunc(b.handleHighlight)
|
||||
|
||||
b.ResetMoves()
|
||||
b.ResetPreMoves()
|
||||
|
||||
b.SetState(initialState)
|
||||
|
||||
// TODO
|
||||
/*
|
||||
b.v[StatePlayerColor] = -1
|
||||
b.v[StateBoardSpace0+11] = 12
|
||||
b.v[StateBoardSpace0+9] = 7
|
||||
b.v[StateBoardSpace0+13] = -13
|
||||
b.v[StateBoardSpace0+24] = -3
|
||||
b.v[StatePlayerBar] = 3
|
||||
b.Update()
|
||||
*/
|
||||
|
||||
b.Update()
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Board) resetSelection() {
|
||||
func (b *GameBoard) Update() {
|
||||
b.TextView.SetBytes(b.Board.Render())
|
||||
}
|
||||
|
||||
func (b *GameBoard) resetSelection() {
|
||||
b.selected[0] = 0
|
||||
b.selected[1] = 0
|
||||
}
|
||||
|
||||
func (b *Board) autoSendMoves() {
|
||||
movable := 2
|
||||
if b.playerDice[0] > 0 && b.playerDice[0] == b.playerDice[1] {
|
||||
movable = 4
|
||||
}
|
||||
if b.v[StateMovablePieces] > 0 {
|
||||
movable = b.v[StateMovablePieces]
|
||||
}
|
||||
if len(b.premove) == 1 {
|
||||
abs := b.premove[0][1] - b.premove[0][0]
|
||||
direction := 1
|
||||
if abs < 0 {
|
||||
abs *= -1
|
||||
direction = -1
|
||||
}
|
||||
if b.playerDice[0] == b.playerDice[1] {
|
||||
for expandDoubles := 4; expandDoubles >= 2; expandDoubles-- {
|
||||
if abs != b.playerDice[0]*expandDoubles {
|
||||
continue
|
||||
}
|
||||
|
||||
from, _ := b.premove[0][0], b.premove[0][1]
|
||||
|
||||
b.premove = nil
|
||||
for i := 1; i <= expandDoubles; i++ {
|
||||
b.premove = append(b.premove, [2]int{from + ((b.playerDice[0]*i - 1) * direction), from + ((b.playerDice[0] * i) * direction)})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(b.premove) < movable {
|
||||
return
|
||||
}
|
||||
|
||||
moveCommand := []byte("move")
|
||||
for j := 0; j < 2; j++ {
|
||||
for i := range b.premove {
|
||||
var from string
|
||||
if b.premove[i][0] == 0 || b.premove[i][0] == 25 {
|
||||
from = "bar"
|
||||
} else {
|
||||
from = strconv.Itoa(b.premove[i][0])
|
||||
}
|
||||
|
||||
if (j == 0) != (from == "bar") {
|
||||
continue // Always send bar moves first
|
||||
}
|
||||
|
||||
var to string
|
||||
if b.premove[i][1] == b.playerHomeSpace() {
|
||||
to = "off"
|
||||
} else {
|
||||
to = strconv.Itoa(b.premove[i][1])
|
||||
}
|
||||
|
||||
moveCommand = append(moveCommand, []byte(" "+from+"-"+to)...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
b.ResetPreMoves()
|
||||
|
||||
b.client.out <- moveCommand
|
||||
}
|
||||
|
||||
func (b *Board) GetState() string {
|
||||
var s = strings.Join(b.s, ":")
|
||||
for i := range b.v {
|
||||
s += ":" + strconv.Itoa(b.v[i])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *Board) SetState(state string) {
|
||||
b.Lock()
|
||||
|
||||
lastTurn := 0
|
||||
if len(b.v) > 0 {
|
||||
lastTurn = b.v[StateTurn]
|
||||
}
|
||||
|
||||
b.s = strings.Split(state, ":")
|
||||
|
||||
b.v = make([]int, len(b.s)-2)
|
||||
var err error
|
||||
for i := 0; i < 46; i++ {
|
||||
b.v[i], err = strconv.Atoi(b.s[i+2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if b.v[StateTurn] != lastTurn {
|
||||
if lastTurn == b.v[StatePlayerColor] {
|
||||
b.playerDice = [2]int{0, 0}
|
||||
} else {
|
||||
b.opponentDice = [2]int{0, 0}
|
||||
}
|
||||
}
|
||||
|
||||
if b.v[StatePlayerDice1] > 0 {
|
||||
b.playerDice = [2]int{b.v[StatePlayerDice1], b.v[StatePlayerDice2]}
|
||||
}
|
||||
if b.v[StateOpponentDice1] > 0 {
|
||||
b.opponentDice = [2]int{b.v[StateOpponentDice1], b.v[StateOpponentDice2]}
|
||||
}
|
||||
|
||||
b.Unlock()
|
||||
b.Update()
|
||||
}
|
||||
|
||||
func (b *Board) renderSpace(index int, spaceValue int) []byte {
|
||||
var playerColor = "x"
|
||||
var opponentColor = "o"
|
||||
if b.v[StatePlayerColor] == 1 {
|
||||
playerColor = "o"
|
||||
opponentColor = "x"
|
||||
}
|
||||
|
||||
var pieceColor string
|
||||
value := b.v[StateBoardSpace0+index]
|
||||
if index == b.playerBarSpace() {
|
||||
value = b.v[StatePlayerBar]
|
||||
pieceColor = playerColor
|
||||
} else if index == 25-b.playerBarSpace() {
|
||||
value = b.v[StateOpponentBar]
|
||||
pieceColor = opponentColor
|
||||
} else {
|
||||
if value < 0 {
|
||||
pieceColor = "x"
|
||||
} else if value > 0 {
|
||||
pieceColor = "o"
|
||||
} else {
|
||||
pieceColor = playerColor
|
||||
}
|
||||
}
|
||||
|
||||
abs := value
|
||||
if value < 0 {
|
||||
abs = value * -1
|
||||
}
|
||||
|
||||
top := index <= 12
|
||||
if b.v[StatePlayerColor] == 1 {
|
||||
top = !top
|
||||
}
|
||||
|
||||
firstDigit := 4
|
||||
secondDigit := 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(abs)
|
||||
}
|
||||
if abs > 9 {
|
||||
secondNumeral = strconv.Itoa(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
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
if abs > 5 {
|
||||
abs = 5
|
||||
}
|
||||
|
||||
var r []byte
|
||||
foregroundColor := "#FFFFFF"
|
||||
backgroundColor := "#000000"
|
||||
if index != 0 && index != 25 {
|
||||
if true { // default theme
|
||||
if index%2 == 0 {
|
||||
backgroundColor = "#303030"
|
||||
} else {
|
||||
backgroundColor = "#101010"
|
||||
}
|
||||
} else { // rainbow
|
||||
foregroundColor = "#000000"
|
||||
switch index % 6 {
|
||||
case 1:
|
||||
backgroundColor = "#FF0000"
|
||||
case 2:
|
||||
backgroundColor = "#FFA500"
|
||||
case 3:
|
||||
backgroundColor = "#FFFF00"
|
||||
case 4:
|
||||
backgroundColor = "#008000"
|
||||
case 5:
|
||||
backgroundColor = "#0000FF"
|
||||
case 0:
|
||||
backgroundColor = "#4B0082"
|
||||
}
|
||||
}
|
||||
}
|
||||
// Highlight legal moves
|
||||
highlightSpace := b.validMove(b.selected[0], index)
|
||||
highlightSpace = false // TODO Make configurable, disable by default
|
||||
//+(b.playerDice[0]*b.v[StatePlayerColor]) ||b.selected[0] == index+(b.playerDice[1]*b.v[StatePlayerColor])) && b.selected[1] > 0
|
||||
if b.selected[1] > 0 && highlightSpace && index != 25 && index != 0 {
|
||||
foregroundColor = "black"
|
||||
backgroundColor = "yellow"
|
||||
}
|
||||
if abs > 0 && spaceValue <= abs {
|
||||
r = []byte(pieceColor)
|
||||
} else {
|
||||
r = []byte(" ")
|
||||
}
|
||||
|
||||
rightArrowFrom := (b.v[StateDirection] == b.movesColor) == (index > 12)
|
||||
if b.selected[0] == index && b.selected[1] > 0 && spaceValue <= abs && spaceValue > abs-b.selected[1] {
|
||||
r = []byte("*")
|
||||
} else if b.premovefrom[index] > 0 && spaceValue > (abs+b.premoveto[index])-b.premovefrom[index] && spaceValue <= abs+b.premoveto[index] {
|
||||
if index == 25-b.playerBarSpace() {
|
||||
r = []byte("▾")
|
||||
} else if index == b.playerBarSpace() {
|
||||
r = []byte("▴")
|
||||
} else if rightArrowFrom {
|
||||
r = []byte("▸")
|
||||
} else {
|
||||
r = []byte("◂")
|
||||
}
|
||||
foregroundColor = "yellow"
|
||||
} else if b.premoveto[index] > 0 && spaceValue > abs && spaceValue <= abs+(b.premoveto[index]) {
|
||||
r = []byte(playerColor)
|
||||
foregroundColor = "yellow"
|
||||
} else if b.from[index] > 0 && spaceValue > abs && spaceValue <= abs+b.from[index] {
|
||||
if rightArrowFrom {
|
||||
r = []byte("▸")
|
||||
} else {
|
||||
r = []byte("◂")
|
||||
}
|
||||
if b.movesColor == b.v[StatePlayerColor] {
|
||||
foregroundColor = "yellow"
|
||||
} else {
|
||||
foregroundColor = "green"
|
||||
}
|
||||
} else if b.to[index] > 0 && spaceValue > abs-(b.to[index]+b.from[index]) {
|
||||
if b.movesColor == b.v[StatePlayerColor] {
|
||||
foregroundColor = "yellow"
|
||||
} else {
|
||||
foregroundColor = "green"
|
||||
}
|
||||
}
|
||||
|
||||
return append(append([]byte(fmt.Sprintf("[\"space-%d\"][%s:%s:b] ", index, foregroundColor, backgroundColor)), r...), []byte(" [-:-:-][\"\"]")...)
|
||||
}
|
||||
|
||||
func (b *Board) Update() {
|
||||
b.Lock()
|
||||
if app != nil {
|
||||
defer app.Draw(b)
|
||||
}
|
||||
|
||||
var white bool
|
||||
if b.v[StatePlayerColor] == 1 {
|
||||
white = true
|
||||
}
|
||||
|
||||
var opponentName = b.s[1]
|
||||
var playerName = b.s[0]
|
||||
|
||||
var playerColor = "x"
|
||||
var opponentColor = "o"
|
||||
if white {
|
||||
playerColor = "o"
|
||||
opponentColor = "x"
|
||||
}
|
||||
|
||||
var t bytes.Buffer
|
||||
t.WriteString("[\"space-off\"] [\"\"]\n")
|
||||
t.WriteString("[\"space-off\"] [\"\"]\n")
|
||||
t.WriteString("[\"space-off\"] ")
|
||||
if white {
|
||||
t.Write(boardTopWhite)
|
||||
} else {
|
||||
t.Write(boardTopBlack)
|
||||
}
|
||||
t.WriteString("[\"\"] ")
|
||||
t.WriteByte('\n')
|
||||
|
||||
space := func(i int, j int) []byte {
|
||||
spaceValue := i + 1
|
||||
if i > 5 {
|
||||
spaceValue = 5 - (i - 6)
|
||||
}
|
||||
|
||||
if j == -1 {
|
||||
if i <= 4 {
|
||||
return b.renderSpace(25-b.playerBarSpace(), spaceValue)
|
||||
}
|
||||
return b.renderSpace(b.playerBarSpace(), spaceValue)
|
||||
}
|
||||
|
||||
var index int
|
||||
if !white {
|
||||
if i < 6 {
|
||||
j = 12 - j
|
||||
} else {
|
||||
j = 11 - j
|
||||
}
|
||||
|
||||
index = 12 + j
|
||||
if i > 5 {
|
||||
index = 12 - j
|
||||
}
|
||||
} else {
|
||||
index = 12 + j
|
||||
if i > 5 {
|
||||
index = 11 - j
|
||||
}
|
||||
}
|
||||
if !white {
|
||||
index = 24 - index
|
||||
}
|
||||
index++ // increment to get actual space number (0 is bar)
|
||||
|
||||
if i == 5 {
|
||||
return []byte("[-:#000000] [-:-]")
|
||||
}
|
||||
|
||||
return b.renderSpace(index, spaceValue)
|
||||
}
|
||||
|
||||
for i := 0; i < 11; i++ {
|
||||
t.Write([]byte("[\"space-off\"]"))
|
||||
|
||||
if i == 5 && b.v[StateDoublingValue] > 1 {
|
||||
t.WriteString(fmt.Sprintf("%2d ", b.v[StateDoublingValue]))
|
||||
if b.v[StatePlayerMayDouble] == 1 {
|
||||
t.WriteByte('v')
|
||||
} else {
|
||||
t.WriteByte('^')
|
||||
}
|
||||
} else {
|
||||
t.WriteByte(' ')
|
||||
t.WriteByte(' ')
|
||||
t.WriteByte(' ')
|
||||
t.WriteByte(' ')
|
||||
}
|
||||
|
||||
t.WriteRune(cview.BoxDrawingsLightVertical)
|
||||
t.Write([]byte("[\"\"]"))
|
||||
for j := 0; j < 12; j++ {
|
||||
t.Write(space(i, j))
|
||||
|
||||
if j == 5 {
|
||||
t.WriteRune(cview.BoxDrawingsLightVertical)
|
||||
t.Write(space(i, -1))
|
||||
t.WriteRune(cview.BoxDrawingsLightVertical)
|
||||
}
|
||||
}
|
||||
|
||||
t.Write([]byte("[\"space-off\"]" + string(cview.BoxDrawingsLightVertical) + " "))
|
||||
|
||||
playerRollColor := "yellow"
|
||||
playerBold := "b"
|
||||
opponentRollColor := "white"
|
||||
opponentBold := ""
|
||||
if b.v[StateTurn] != b.v[StatePlayerColor] {
|
||||
playerRollColor = "white"
|
||||
opponentRollColor = "green"
|
||||
playerBold = ""
|
||||
opponentBold = "b"
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
t.Write([]byte("[" + opponentRollColor + "::" + opponentBold + "]" + opponentColor + " " + opponentName + " (" + b.s[4] + ")"))
|
||||
if b.v[StateOpponentHome] > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" %d off", b.v[StateOpponentHome])))
|
||||
}
|
||||
t.Write([]byte("[-::-]"))
|
||||
} else if i == 2 {
|
||||
if b.opponentDice[0] > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", opponentRollColor, opponentBold, b.opponentDice[0], b.opponentDice[1])))
|
||||
} else {
|
||||
t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", opponentRollColor)))
|
||||
}
|
||||
} else if i == 8 {
|
||||
if b.playerDice[0] > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" [%s::%s]%d %d[-::-] ", playerRollColor, playerBold, b.playerDice[0], b.playerDice[1])))
|
||||
} else {
|
||||
t.Write([]byte(fmt.Sprintf(" [%s]- -[-] ", playerRollColor)))
|
||||
}
|
||||
} else if i == 10 {
|
||||
t.Write([]byte("[" + playerRollColor + "::" + playerBold + "]" + playerColor + " " + playerName + " (" + b.s[3] + ")"))
|
||||
if b.v[StatePlayerHome] > 0 {
|
||||
t.Write([]byte(fmt.Sprintf(" %d off", b.v[StatePlayerHome])))
|
||||
}
|
||||
t.Write([]byte("[-::-]"))
|
||||
}
|
||||
|
||||
t.Write([]byte("[\"\"] "))
|
||||
t.WriteByte('\n')
|
||||
}
|
||||
|
||||
t.WriteString("[\"space-off\"] ")
|
||||
if white {
|
||||
t.Write(boardBottomWhite)
|
||||
} else {
|
||||
t.Write(boardBottomBlack)
|
||||
}
|
||||
t.WriteString(" [\"\"]\n")
|
||||
t.WriteString("[\"space-off\"] [\"\"]")
|
||||
|
||||
b.Unlock()
|
||||
b.TextView.SetBytes(t.Bytes())
|
||||
}
|
||||
|
||||
func (b *Board) ResetMoves() {
|
||||
b.moves = nil
|
||||
b.movesColor = 0
|
||||
b.validMoves = make(map[int][]int)
|
||||
b.from = make(map[int]int)
|
||||
b.to = make(map[int]int)
|
||||
}
|
||||
|
||||
func (b *Board) ResetPreMoves() {
|
||||
b.premove = nil
|
||||
b.premovefrom = make(map[int]int)
|
||||
b.premoveto = make(map[int]int)
|
||||
}
|
||||
|
||||
func (b *Board) allPlayerPiecesInHomeBoard() bool {
|
||||
homeBoardStart := 1
|
||||
homeBoardEnd := 6
|
||||
if b.v[StateDirection] == -1 {
|
||||
homeBoardStart = 19
|
||||
homeBoardEnd = 24
|
||||
}
|
||||
hasPlayerPiece := func(index int) bool {
|
||||
if index < 0 || index > 25 {
|
||||
return false
|
||||
}
|
||||
return (b.v[StatePlayerColor] == 1 && b.v[StateBoardSpace0+index] > 0) ||
|
||||
(b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] < 0)
|
||||
}
|
||||
for i := 1; i < 24; i++ {
|
||||
if i >= homeBoardStart && i <= homeBoardEnd {
|
||||
continue
|
||||
}
|
||||
if hasPlayerPiece(i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Board) spaceAvailable(index int) bool {
|
||||
if index < 0 || index > 25 {
|
||||
return false
|
||||
}
|
||||
if index == 0 || index == 25 {
|
||||
return b.allPlayerPiecesInHomeBoard()
|
||||
}
|
||||
return (b.v[StatePlayerColor] == 1 && b.v[StateBoardSpace0+index] >= -1) ||
|
||||
(b.v[StatePlayerColor] == -1 && b.v[StateBoardSpace0+index] <= 1)
|
||||
}
|
||||
|
||||
func (b *Board) GetValidMoves(from int) []int {
|
||||
if validMoves, ok := b.validMoves[from]; ok {
|
||||
return validMoves
|
||||
}
|
||||
|
||||
var validMoves []int
|
||||
defer func() {
|
||||
b.validMoves[from] = validMoves
|
||||
}()
|
||||
|
||||
if b.v[StateTurn] != b.v[StatePlayerColor] || b.playerDice[0] == 0 || b.playerDice[1] == 0 {
|
||||
return validMoves
|
||||
}
|
||||
|
||||
// TODO consider opponent blocking midway in full move
|
||||
trySpaces := [][]int{
|
||||
{b.playerDice[0]},
|
||||
{b.playerDice[1]},
|
||||
{b.playerDice[0], b.playerDice[1]},
|
||||
{b.playerDice[1], b.playerDice[0]},
|
||||
}
|
||||
if b.playerDice[0] == b.playerDice[1] {
|
||||
trySpaces = append(trySpaces,
|
||||
[]int{b.playerDice[0], b.playerDice[0], b.playerDice[0]},
|
||||
[]int{b.playerDice[0], b.playerDice[0], b.playerDice[0], b.playerDice[0]})
|
||||
}
|
||||
|
||||
if b.allPlayerPiecesInHomeBoard() {
|
||||
homeSpace := b.playerHomeSpace()
|
||||
spacesHome := from - homeSpace
|
||||
if spacesHome < 0 {
|
||||
spacesHome *= -1
|
||||
}
|
||||
if spacesHome <= b.playerDice[0] || spacesHome <= b.playerDice[1] {
|
||||
trySpaces = append(trySpaces, []int{spacesHome})
|
||||
}
|
||||
}
|
||||
foundMoves := make(map[int]bool)
|
||||
CHECKSPACES:
|
||||
for i := range trySpaces {
|
||||
checkSpace := 0
|
||||
for _, space := range trySpaces[i] {
|
||||
checkSpace += space
|
||||
if !b.spaceAvailable(from + (checkSpace * b.v[StateDirection])) {
|
||||
continue CHECKSPACES
|
||||
}
|
||||
}
|
||||
space := from + (checkSpace * b.v[StateDirection])
|
||||
if _, value := foundMoves[space]; !value {
|
||||
foundMoves[space] = true
|
||||
validMoves = append(validMoves, space)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Ints(validMoves)
|
||||
|
||||
return validMoves
|
||||
}
|
||||
|
||||
func (b *Board) playerBarSpace() int {
|
||||
return 25 - b.playerHomeSpace()
|
||||
}
|
||||
|
||||
func (b *Board) playerHomeSpace() int {
|
||||
if b.v[StateDirection] == -1 {
|
||||
return 0
|
||||
}
|
||||
return 25
|
||||
}
|
||||
|
||||
func (b *Board) validMove(f int, t int) bool {
|
||||
if b.v[StateTurn] != b.v[StatePlayerColor] || b.playerDice[0] == 0 || b.playerDice[1] == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if t == b.playerHomeSpace() {
|
||||
// TODO bear off logic, only allow high roll
|
||||
return b.allPlayerPiecesInHomeBoard()
|
||||
}
|
||||
|
||||
validMoves := b.GetValidMoves(f)
|
||||
for i := range validMoves {
|
||||
if validMoves[i] == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Board) Move(player int, f string, t string) {
|
||||
from, err := strconv.Atoi(f)
|
||||
if err != nil {
|
||||
from = SpaceUnknown
|
||||
if f == "bar" {
|
||||
barSpace := b.playerBarSpace()
|
||||
if b.v[StatePlayerColor] == player {
|
||||
from = barSpace
|
||||
} else {
|
||||
from = 25 - barSpace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
to = SpaceUnknown
|
||||
if t == "off" {
|
||||
to = b.playerHomeSpace()
|
||||
}
|
||||
}
|
||||
|
||||
if from == SpaceUnknown || to == SpaceUnknown {
|
||||
lf("error: failed to parse move: player %d, from %s, to %s", player, f, t)
|
||||
return
|
||||
}
|
||||
|
||||
b.moves = append(b.moves, [2]int{from, to})
|
||||
b.movesColor = player
|
||||
|
||||
b.from[from]++
|
||||
b.to[to]++
|
||||
|
||||
b.v[StateTurn] = player * -1
|
||||
|
||||
b.validMoves = make(map[int][]int)
|
||||
b.ResetPreMoves()
|
||||
}
|
||||
|
||||
func (b *Board) SimplifyMoves() {
|
||||
for i := range b.moves {
|
||||
for j := range b.moves {
|
||||
if b.moves[i][1] == b.moves[j][0] {
|
||||
// Same to space as from space
|
||||
b.moves[j][0] = b.moves[i][0] // Set from space to earlier from space
|
||||
b.moves = append(b.moves[:i], b.moves[i+1:]...)
|
||||
b.SimplifyMoves()
|
||||
return
|
||||
} else if b.moves[i][0] == b.moves[j][1] {
|
||||
// Same to space as from space
|
||||
b.moves[j][1] = b.moves[i][1] // Set to space to earlier to space
|
||||
b.moves = append(b.moves[:i], b.moves[i+1:]...)
|
||||
b.SimplifyMoves()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Board) addPreMove(from int, to int) bool {
|
||||
if !b.validMove(from, to) {
|
||||
return false
|
||||
}
|
||||
b.premove = append(b.premove, [2]int{from, to})
|
||||
b.premovefrom[from]++
|
||||
b.premoveto[to]++
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Board) handleHighlight(added, removed, remaining []string) {
|
||||
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
|
||||
|
@ -780,7 +71,7 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
to, err := strconv.Atoi(added[0][6:])
|
||||
if err != nil {
|
||||
if added[0] == "space-off" {
|
||||
to = b.playerHomeSpace()
|
||||
to = b.Board.PlayerHomeSpace()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
@ -796,17 +87,20 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
}
|
||||
}
|
||||
var mid = SpaceUnknown
|
||||
if (b.playerDice[0] != b.playerDice[1] && spaces*b.selected[1] == b.playerDice[0]+b.playerDice[1]) ||
|
||||
(b.playerDice[0] == b.playerDice[1] && spaces*b.selected[1] == (b.playerDice[0]+b.playerDice[1])*2) {
|
||||
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 := b.playerDice[i]
|
||||
index := calcFrom + (dice * b.v[StateDirection])
|
||||
dice := v[fibs.StatePlayerDice1]
|
||||
if i == 2 {
|
||||
dice = v[fibs.StatePlayerDice2]
|
||||
}
|
||||
index := calcFrom + (dice * v[fibs.StateDirection])
|
||||
if index == to {
|
||||
continue
|
||||
}
|
||||
if (b.v[StateBoardSpace0+index] == -1 && b.v[StatePlayerColor] > 0) ||
|
||||
(b.v[StateBoardSpace0+index] == 1 && b.v[StatePlayerColor] < 0) {
|
||||
if (v[fibs.StateBoardSpace0+index] == -1 && v[fibs.StatePlayerColor] > 0) ||
|
||||
(v[fibs.StateBoardSpace0+index] == 1 && v[fibs.StatePlayerColor] < 0) {
|
||||
mid = index
|
||||
break
|
||||
}
|
||||
|
@ -814,13 +108,16 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
if mid == SpaceUnknown {
|
||||
// Send any valid move
|
||||
for i := 0; i < 2; i++ {
|
||||
dice := b.playerDice[i]
|
||||
index := calcFrom + (dice * b.v[StateDirection])
|
||||
dice := v[fibs.StatePlayerDice1]
|
||||
if i == 2 {
|
||||
dice = v[fibs.StatePlayerDice2]
|
||||
}
|
||||
index := calcFrom + (dice * v[fibs.StateDirection])
|
||||
if index == to {
|
||||
continue
|
||||
}
|
||||
if (b.v[StateBoardSpace0+index] >= 0 && b.v[StatePlayerColor] > 0) ||
|
||||
(b.v[StateBoardSpace0+index] <= 0 && b.v[StatePlayerColor] < 0) {
|
||||
if (v[fibs.StateBoardSpace0+index] >= 0 && v[fibs.StatePlayerColor] > 0) ||
|
||||
(v[fibs.StateBoardSpace0+index] <= 0 && v[fibs.StatePlayerColor] < 0) {
|
||||
mid = index
|
||||
break
|
||||
}
|
||||
|
@ -830,10 +127,10 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
|
||||
for i := 0; i < b.selected[1]; i++ {
|
||||
if mid < 0 {
|
||||
b.addPreMove(from, to)
|
||||
b.Board.AddPreMove(from, to)
|
||||
} else {
|
||||
b.addPreMove(from, mid)
|
||||
b.addPreMove(mid, to)
|
||||
b.Board.AddPreMove(from, mid)
|
||||
b.Board.AddPreMove(mid, to)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -843,8 +140,6 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
b.TextView.SetHighlightedFunc(b.handleHighlight)
|
||||
|
||||
b.resetSelection()
|
||||
|
||||
b.autoSendMoves()
|
||||
} else if len(added) > 0 {
|
||||
if added[0] == "space-off" {
|
||||
b.TextView.SetHighlightedFunc(nil)
|
||||
|
@ -853,7 +148,7 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
|
||||
b.resetSelection()
|
||||
return
|
||||
} else if (added[0] == "space-0" || added[0] == "space-25") && b.v[StatePlayerBar] == 0 {
|
||||
} 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)
|
||||
|
@ -864,12 +159,12 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
|
||||
index, err := strconv.Atoi(added[0][6:])
|
||||
if err == nil {
|
||||
abs := b.v[StateBoardSpace0+index]
|
||||
abs := v[fibs.StateBoardSpace0+index]
|
||||
if abs < 0 {
|
||||
abs *= -1
|
||||
}
|
||||
if added[0] == "space-0" || added[0] == "space-25" {
|
||||
abs = b.v[StatePlayerBar]
|
||||
abs = v[fibs.StatePlayerBar]
|
||||
}
|
||||
|
||||
if b.selected[1] >= abs && false { // TODO or has premove piece in space
|
||||
|
@ -885,12 +180,12 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
} else if len(removed) > 0 {
|
||||
index, err := strconv.Atoi(removed[0][6:])
|
||||
if err == nil {
|
||||
abs := b.v[StateBoardSpace0+index]
|
||||
abs := v[fibs.StateBoardSpace0+index]
|
||||
if abs < 0 {
|
||||
abs *= -1
|
||||
}
|
||||
if removed[0] == "space-0" || removed[0] == "space-25" {
|
||||
abs = b.v[StatePlayerBar]
|
||||
abs = v[fibs.StatePlayerBar]
|
||||
}
|
||||
|
||||
if b.selected[1] < abs {
|
||||
|
@ -906,7 +201,7 @@ func (b *Board) handleHighlight(added, removed, remaining []string) {
|
|||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (b *Board) MouseHandler() func(action cview.MouseAction, event *tcell.EventMouse, setFocus func(p cview.Primitive)) (consumed bool, capture cview.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) {
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBoard_GetValidMoves(t *testing.T) {
|
||||
client := NewClient("", "")
|
||||
|
||||
board = NewBoard(client)
|
||||
|
||||
b := board
|
||||
b.v[StatePlayerColor] = 1
|
||||
b.v[StateTurn] = 1
|
||||
b.v[StateDirection] = -1
|
||||
b.v[StateBoardSpace0+11] = 3
|
||||
b.v[StateBoardSpace0+9] = 7
|
||||
b.v[StateBoardSpace0+13] = -4
|
||||
b.v[StateBoardSpace0+24] = -3
|
||||
b.v[StatePlayerBar] = 1
|
||||
|
||||
testCases := []struct {
|
||||
roll [2]int
|
||||
from int
|
||||
moves []int
|
||||
}{
|
||||
{
|
||||
roll: [2]int{1, 5},
|
||||
from: 25,
|
||||
moves: []int{19, 20},
|
||||
},
|
||||
{
|
||||
roll: [2]int{1, 5},
|
||||
from: 1,
|
||||
moves: []int{},
|
||||
},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(fmt.Sprintf("With-%d-%d-From-%s", c.roll[0], c.roll[1], fmt.Sprintf("%02d", c.from)), func(t *testing.T) {
|
||||
b.playerDice = c.roll
|
||||
b.Update()
|
||||
|
||||
validMoves := board.GetValidMoves(c.from)
|
||||
if !equalInts(validMoves, c.moves) {
|
||||
t.Errorf("unexpected valid moves: expected %+v, got %+v\n%s", c.moves, validMoves, b.GetBytes(true))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func equalInts(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
621
client.go
621
client.go
|
@ -1,621 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"code.rocketnine.space/tslocum/cview"
|
||||
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
const whoInfoSize = 12
|
||||
|
||||
const (
|
||||
whoInfoDataName = iota
|
||||
whoInfoDataOpponent
|
||||
whoInfoDataWatching
|
||||
whoInfoDataReady
|
||||
whoInfoDataAway
|
||||
whoInfoDataRating
|
||||
whoInfoDataExperience
|
||||
whoInfoDataIdleTime
|
||||
whoInfoDataLoginTime
|
||||
whoInfoDataHostname
|
||||
whoInfoDataClientName
|
||||
whoInfoDataEmail
|
||||
)
|
||||
|
||||
var (
|
||||
TypeWelcome = []byte("1")
|
||||
TypeOwnInfo = []byte("2")
|
||||
TypeMOTD = []byte("3")
|
||||
TypeEndMOTD = []byte("4")
|
||||
TypeWhoInfo = []byte("5")
|
||||
TypeEndWhoInfo = []byte("6")
|
||||
TypeLogin = []byte("7")
|
||||
TypeLogout = []byte("8")
|
||||
TypeMsg = []byte("9")
|
||||
TypeMsgDelivered = []byte("10")
|
||||
TypeMsgSaved = []byte("11")
|
||||
TypeSay = []byte("12")
|
||||
TypeShout = []byte("13")
|
||||
TypeWhisper = []byte("14")
|
||||
TypeKibitz = []byte("15")
|
||||
TypeYouSay = []byte("16")
|
||||
TypeYouShout = []byte("17")
|
||||
TypeYouWhisper = []byte("18")
|
||||
TypeYouKibitz = []byte("19")
|
||||
TypeBoardState = []byte("board:")
|
||||
)
|
||||
|
||||
var numberPrinter = message.NewPrinter(language.English)
|
||||
|
||||
type WhoInfo struct {
|
||||
username string
|
||||
opponent string
|
||||
watching string
|
||||
ready bool
|
||||
away bool
|
||||
rating int
|
||||
experience int
|
||||
idle int
|
||||
loginTime int
|
||||
clientName string
|
||||
|
||||
listItem *cview.ListItem
|
||||
}
|
||||
|
||||
func (w *WhoInfo) String() string {
|
||||
opponent := "in the lobby"
|
||||
if w.opponent != "" && w.opponent != "-" {
|
||||
opponent = "playing against " + w.opponent
|
||||
}
|
||||
clientName := ""
|
||||
if w.clientName != "" && w.clientName != "-" {
|
||||
clientName = " using " + w.clientName
|
||||
}
|
||||
return fmt.Sprintf("%s (rated %d with %d exp) is %s%s", w.username, w.rating, w.experience, opponent, clientName)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
in chan []byte
|
||||
out chan []byte
|
||||
|
||||
username string
|
||||
password string
|
||||
|
||||
loggedin bool
|
||||
motd []byte
|
||||
rawMode bool
|
||||
|
||||
who map[string]*WhoInfo
|
||||
|
||||
notified map[string]bool
|
||||
}
|
||||
|
||||
func NewClient(username string, password string) *Client {
|
||||
return &Client{
|
||||
in: make(chan []byte, 100),
|
||||
out: make(chan []byte, 100),
|
||||
|
||||
username: username,
|
||||
password: password,
|
||||
|
||||
who: make(map[string]*WhoInfo),
|
||||
|
||||
notified: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// CallTELNET is called when a connection is made with the server.
|
||||
func (c *Client) CallTELNET(ctx telnet.Context, w telnet.Writer, r telnet.Reader) {
|
||||
go func() {
|
||||
var b = &bytes.Buffer{}
|
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:]
|
||||
|
||||
var motd bool // Parse all messages as MOTD text until MsgEndMOTD
|
||||
|
||||
for {
|
||||
// Read 1 byte.
|
||||
n, err := r.Read(p)
|
||||
if n <= 0 && nil == err {
|
||||
continue
|
||||
} else if n <= 0 && nil != err {
|
||||
if err.Error() != "EOF" {
|
||||
lf("** Disconnected: %s", err)
|
||||
} else {
|
||||
l("** Disconnected")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteByte(p[0])
|
||||
|
||||
if p[0] == '\n' {
|
||||
buf := make([]byte, b.Len())
|
||||
copy(buf, b.Bytes())
|
||||
|
||||
if debug > 0 {
|
||||
l("<- " + string(bytes.TrimSpace(buf)))
|
||||
}
|
||||
|
||||
if c.loggedin {
|
||||
if !motd {
|
||||
buf = bytes.TrimSpace(buf)
|
||||
if len(buf) == 0 {
|
||||
b.Reset()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if c.rawMode {
|
||||
c.in <- append([]byte("** "), buf...)
|
||||
b.Reset()
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(b.Bytes(), TypeMOTD) && !motd {
|
||||
motd = true
|
||||
c.motd = append(c.motd, buf[1:]...)
|
||||
b.Reset()
|
||||
continue
|
||||
} else if bytes.HasPrefix(b.Bytes(), TypeEndMOTD) && motd {
|
||||
motd = false
|
||||
c.motd = bytes.TrimSpace(c.motd)
|
||||
c.in <- append([]byte("3 "), c.motd...)
|
||||
b.Reset()
|
||||
continue
|
||||
} else if motd {
|
||||
c.motd = append(c.motd, buf...)
|
||||
b.Reset()
|
||||
continue
|
||||
}
|
||||
|
||||
c.in <- buf
|
||||
}
|
||||
b.Reset()
|
||||
}
|
||||
|
||||
if !c.loggedin {
|
||||
bt := strings.TrimSpace(b.String())
|
||||
if bt == "login:" {
|
||||
b.Reset()
|
||||
c.out <- []byte(fmt.Sprintf("login bgammon 1008 %s %s", c.username, c.password))
|
||||
c.loggedin = true
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
app.QueueUpdateDraw(func() {
|
||||
statusBuffer.ScrollToEnd()
|
||||
})
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var p []byte
|
||||
|
||||
var crlfBuffer [2]byte = [2]byte{'\r', '\n'}
|
||||
crlf := crlfBuffer[:]
|
||||
|
||||
help := []byte("help")
|
||||
who := []byte("who")
|
||||
quit := []byte("quit")
|
||||
bye := []byte("bye")
|
||||
watch := []byte("watch")
|
||||
about := []byte("about")
|
||||
show := []byte("show")
|
||||
average := []byte("average")
|
||||
dicetest := []byte("dicetest")
|
||||
boardstate := []byte("boardstate")
|
||||
stat := []byte("stat")
|
||||
for b := range c.out {
|
||||
if bytes.Equal(bytes.ToLower(b), watch) {
|
||||
c.watchRandomGame()
|
||||
continue
|
||||
} else if bytes.Equal(bytes.ToLower(b), who) {
|
||||
for username := range c.who {
|
||||
lf("%s", c.who[username])
|
||||
}
|
||||
continue
|
||||
} else if bytes.HasPrefix(bytes.ToLower(b), help) || bytes.HasPrefix(bytes.ToLower(b), about) || bytes.HasPrefix(bytes.ToLower(b), average) ||
|
||||
bytes.HasPrefix(bytes.ToLower(b), dicetest) || bytes.HasPrefix(bytes.ToLower(b), show) || bytes.HasPrefix(bytes.ToLower(b), stat) {
|
||||
c.rawMode = true
|
||||
go func() {
|
||||
time.Sleep( |