Browse Source

Fix matching turn messages

master
Trevor Slocum 2 months ago
parent
commit
f029843bf9
  1. 165
      app.go
  2. 7
      board.go
  3. 129
      client.go
  4. 4
      go.mod
  5. 7
      go.sum

165
app.go

@ -1,6 +1,8 @@
package main
import (
"sort"
"strings"
"time"
"code.rocketnine.space/tslocum/cview"
@ -15,7 +17,8 @@ const (
var (
app *cview.Application
uiGrid *cview.Grid
lobby *cview.List
userList *cview.List
userListGrid *cview.Grid
board *Board
actionBuffer *cview.TextView
gameBuffer *cview.TextView
@ -29,6 +32,7 @@ var (
chatMode int
viewScreen int
screenX int
)
const (
@ -78,15 +82,80 @@ func logIn(c *Client) {
go c.connect()
}
func setScreen(screen int) {
func setScreen(c *Client, screen int) {
viewScreen = screen
if viewScreen == ScreenLobby {
updateUserList(c)
board.SetVisible(false)
lobby.SetVisible(true)
userListGrid.SetVisible(true)
} else {
lobby.SetVisible(false)
userListGrid.SetVisible(false)
board.SetVisible(true)
}
buildLayout(c)
}
func updateUserList(c *Client) {
var infos []*WhoInfo
for _, whoInfo := range c.who {
infos = append(infos, whoInfo)
}
sort.Slice(infos, func(i, j int) bool {
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].ready != infos[j].ready {
return infos[i].ready
}
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)
})
userList.Clear()
for _, whoInfo := range infos {
userList.AddItem(whoInfo.listItem)
}
}
func buildLayout(c *Client) {
uiGrid.Clear()
var currentScreen cview.Primitive
if viewScreen == ScreenLobby {
currentScreen = userListGrid
} else {
currentScreen = board
}
// Single column on smaller screens
if screenX < 156 {
uiGrid.SetRows(16, 1, -1, 1, -1, 1)
uiGrid.AddItem(currentScreen, 0, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(cview.NewBox(), 1, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(gameBuffer, 2, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(cview.NewBox(), 3, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(statusBuffer, 4, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(inputField, 5, 0, 1, 1, 0, 0, true)
return
}
uiGrid.SetRows(16, 1, -1, 1)
uiGrid.SetColumns(-1, 1, -1)
uiGrid.AddItem(currentScreen, 0, 0, 1, 3, 0, 0, false)
uiGrid.AddItem(cview.NewBox(), 1, 0, 1, 3, 0, 0, false)
uiGrid.AddItem(gameBuffer, 2, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(cview.NewBox(), 2, 1, 1, 1, 0, 0, false)
uiGrid.AddItem(statusBuffer, 2, 2, 1, 1, 0, 0, false)
uiGrid.AddItem(inputField, 3, 0, 1, 3, 0, 0, true)
}
func RunApp(c *Client) error {
@ -194,6 +263,22 @@ func RunApp(c *Client) error {
return event
}
if viewScreen == ScreenLobby {
switch event.Key() {
case tcell.KeyUp:
// Transform user list
}
}
if event.Key() == tcell.KeyESC {
newScreen := ScreenLobby
if viewScreen == ScreenLobby {
newScreen = ScreenGame
}
setScreen(c, newScreen)
return nil
}
if event.Key() == tcell.KeyTab {
if chatMode == ChatModeShout {
chatMode = ChatModeKibitz
@ -225,52 +310,48 @@ func RunApp(c *Client) error {
statusWriter = &bufferWriter{Buffer: statusBuffer}
gameWriter = &bufferWriter{Buffer: gameBuffer}
lobby = cview.NewList()
lobby.AddItem(cview.NewListItem("test"))
userList = cview.NewList()
userList.ShowSecondaryText(false)
userList.SetHighlightFullLine(true)
userList.AddContextItem("Invite", 'i', func(index int) {
buildLayout := func(screenX int, screenY int) {
box := cview.NewBox()
})
userList.AddContextItem("Watch", 'w', func(index int) {
uiGrid = cview.NewGrid()
})
userList.AddContextItem("Message", 'm', func(index int) {
// TODO refactor
defer func() {
if c.username != "" && c.password != "" {
app.SetRoot(uiGrid, true)
app.SetFocus(inputField)
}
}()
})
userList.SetSelectedFunc(func(i int, item *cview.ListItem) {
lf("%d", i)
})
var currentScreen cview.Primitive
if viewScreen == ScreenLobby {
currentScreen = lobby
} else {
currentScreen = board
}
userListHeader := cview.NewTextView()
userListHeader.SetText("[yellow::b]Player Rating Experience Status[-:-:-]")
userListHeader.SetDynamicColors(true)
// Single column on smaller screens
if screenX < 156 {
uiGrid.SetRows(17, -1, 1, -1, 1)
uiGrid.AddItem(currentScreen, 0, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(gameBuffer, 1, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(box, 2, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(statusBuffer, 3, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(inputField, 4, 0, 1, 1, 0, 0, true)
return
}
userListGrid = cview.NewGrid()
userListGrid.SetRows(1, -1)
userListGrid.AddItem(userListHeader, 0, 0, 1, 1, 0, 0, false)
userListGrid.AddItem(userList, 1, 0, 1, 1, 0, 0, true)
uiGrid.SetRows(17, -1, 1)
uiGrid.SetColumns(-1, 1, -1)
uiGrid.AddItem(currentScreen, 0, 0, 1, 3, 0, 0, false)
uiGrid.AddItem(gameBuffer, 1, 0, 1, 1, 0, 0, false)
uiGrid.AddItem(statusBuffer, 1, 2, 1, 1, 0, 0, false)
uiGrid.AddItem(inputField, 2, 0, 1, 3, 0, 0, true)
}
uiGrid = cview.NewGrid()
app.SetAfterResizeFunc(func(width int, height int) {
screenX = width
buildLayout(c)
})
buildLayout(200, 200) // Initial build, resize is called later
app.SetAfterResizeFunc(buildLayout)
// TODO refactor
buildLayout(c)
defer func() {
if c.username != "" && c.password != "" {
app.SetRoot(uiGrid, true)
app.SetFocus(inputField)
}
}()
setScreen(ScreenGame)
setScreen(c, ScreenGame)
lg("+---------------------------------------------------+")
lg("| |")

7
board.go

@ -406,8 +406,8 @@ func (b *Board) Update() {
}
var t bytes.Buffer
t.WriteString("[\"space-off\"] [\"\"] \n")
t.WriteString("[\"space-off\"] [\"\"] \n")
t.WriteString("[\"space-off\"] [\"\"]\n")
t.WriteString("[\"space-off\"] [\"\"]\n")
t.WriteString("[\"space-off\"] ")
if white {
t.Write(boardTopWhite)
@ -538,8 +538,7 @@ func (b *Board) Update() {
} else {
t.Write(boardBottomBlack)
}
t.WriteString(" [\"\"] \n")
t.WriteString("[\"space-off\"] [\"\"] \n")
t.WriteString(" [\"\"]\n")
t.WriteString("[\"space-off\"] [\"\"]")
b.Unlock()

129
client.go

@ -10,10 +10,32 @@ import (
"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")
@ -37,12 +59,21 @@ var (
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
opponent string
agent string
idle int
loginTime int
clientName string
listItem *cview.ListItem
}
func (w *WhoInfo) String() string {
@ -51,8 +82,8 @@ func (w *WhoInfo) String() string {
opponent = "playing against " + w.opponent
}
clientName := ""
if w.agent != "" && w.agent != "-" {
clientName = " using " + w.agent
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)
}
@ -251,46 +282,90 @@ func (c *Client) keepAlive() {
func (c *Client) updateWhoInfo(b []byte) {
s := bytes.Split(b, []byte(" "))
if len(s) != 12 {
if len(s) != whoInfoSize {
return
}
r := s[5]
var listItem *cview.ListItem
if existingInfo, ok := c.who[string(s[whoInfoDataName])]; ok {
listItem = existingInfo.listItem
}
info := &WhoInfo{
username: string(s[whoInfoDataName]),
listItem: listItem,
}
r := s[whoInfoDataRating]
if bytes.ContainsRune(r, '.') {
r = s[5][:bytes.IndexByte(s[5], '.')]
r = s[whoInfoDataRating][:bytes.IndexByte(s[whoInfoDataRating], '.')]
}
rating, err := strconv.Atoi(string(r))
if err != nil {
rating = 0
}
experience, err := strconv.Atoi(string(s[6]))
info.rating = rating
experience, err := strconv.Atoi(string(s[whoInfoDataExperience]))
if err != nil {
experience = 0
}
info.experience = experience
opponent := ""
if len(s[1]) > 1 || s[1][0] != '-' {
opponent = string(s[1])
if len(s[whoInfoDataOpponent]) > 1 || s[whoInfoDataOpponent][0] != '-' {
opponent = string(s[whoInfoDataOpponent])
}
agent := ""
if len(s[10]) > 1 || s[10][0] != '-' {
agent = string(s[10])
info.opponent = opponent
watching := ""
if len(s[whoInfoDataWatching]) > 1 || s[whoInfoDataWatching][0] != '-' {
watching = string(s[whoInfoDataWatching])
}
info.watching = watching
info := &WhoInfo{
username: string(s[0]),
rating: rating,
experience: experience,
opponent: opponent,
agent: agent,
ready := false
if string(s[whoInfoDataReady]) == "1" {
ready = true
}
info.ready = ready
clientName := ""
if len(s[whoInfoDataClientName]) > 1 || s[whoInfoDataClientName][0] != '-' {
clientName = string(s[whoInfoDataClientName])
}
c.who[string(s[0])] = info
info.clientName = clientName
status := "Unavailable"
if info.opponent != "" {
status = "vs. " + info.opponent
} else if info.ready {
status = "Available"
}
itemText := info.username + strings.Repeat(" ", 18-len(info.username))
ratingString := numberPrinter.Sprintf("%d", info.rating)
itemText += ratingString + strings.Repeat(" ", 8-len(ratingString))
if notify && info.username != c.username && info.agent == "bgammon" && !c.notified[info.username] {
experienceString := numberPrinter.Sprintf("%d", info.experience)
itemText += experienceString + strings.Repeat(" ", 12-len(experienceString))
itemText += status
if info.listItem != nil {
info.listItem.SetMainText(itemText)
} else {
info.listItem = cview.NewListItem(itemText)
}
c.who[string(s[whoInfoDataName])] = info
if notify && info.username != c.username && info.clientName == "bgammon" && !c.notified[info.username] {
lf("** %s is also playing via bgammon.", info.username)
c.notified[info.username] = true
}
}
func (c *Client) watchRandomGame() {
@ -345,6 +420,7 @@ func (c *Client) eventLoop() {
var acceptDoubleRegexp = regexp.MustCompile(`^\w+ accepts the double.`)
for b := range c.in {
b = bytes.Replace(b, []byte{7}, []byte{}, -1)
b = bytes.TrimSpace(b)
// Select 10+ first to read prefixes correctly
@ -409,6 +485,9 @@ func (c *Client) eventLoop() {
// TODO who window
continue
} else if bytes.HasPrefix(b, TypeEndWhoInfo) {
if viewScreen == ScreenLobby {
app.Draw() // Show changes
}
continue
} else if bytes.HasPrefix(b, TypeLogin) || bytes.HasPrefix(b, TypeLogout) {
b = b[2:]
@ -485,11 +564,11 @@ func (c *Client) eventLoop() {
continue
} else if string(b) == "Value of 'boardstyle' set to 3." {
continue
} else if string(b) == "It's your turn to move." || string(b) == "It's your turn. Please roll or double" {
} else if string(b) == "It's your turn to move." || strings.TrimSpace(string(b)) == "It's your turn to roll or double." || strings.TrimSpace(string(b)) == "It's your turn. Please roll or double" {
board.v[StateTurn] = board.v[StatePlayerColor]
board.Update()
if string(b) == "It's your turn. Please roll or double" {
c.out <- []byte("/roll") // TODO Delay and allow configuring
if strings.TrimSpace(string(b)) == "It's your turn to roll or double." || strings.TrimSpace(string(b)) == "It's your turn. Please roll or double" {
c.out <- []byte("roll") // TODO Delay and allow configuring
}
} else if cantMoveRegexp.Match(b) {
match := cantMoveRegexp.FindSubmatch(b)

4
go.mod

@ -3,8 +3,10 @@ module code.rocketnine.space/tslocum/bgammon
go 1.16
require (
code.rocketnine.space/tslocum/cview v1.5.7-0.20210802195940-6758d2a78710
code.rocketnine.space/tslocum/cview v1.5.7-0.20210809003029-be5c1fcafd09
github.com/gdamore/tcell/v2 v2.4.0
github.com/reiver/go-oi v1.0.0
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/text v0.3.6
)

7
go.sum

@ -1,7 +1,7 @@
code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE=
code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M=
code.rocketnine.space/tslocum/cview v1.5.7-0.20210802195940-6758d2a78710 h1:IMMqctbCiuV8kxWyKMwfvrzJA7f6UjThjYLl9BTjbxU=
code.rocketnine.space/tslocum/cview v1.5.7-0.20210802195940-6758d2a78710/go.mod h1:sXiz0JZgcL4/bQZDVoel4PRPZRgaZOCvqrhuyQaS3J0=
code.rocketnine.space/tslocum/cview v1.5.7-0.20210809003029-be5c1fcafd09 h1:PGw4Z+3Ug1ipGrnGrguM8CuvFMyvevacy0m7xYJWLbg=
code.rocketnine.space/tslocum/cview v1.5.7-0.20210809003029-be5c1fcafd09/go.mod h1:sXiz0JZgcL4/bQZDVoel4PRPZRgaZOCvqrhuyQaS3J0=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
@ -23,8 +23,9 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=

Loading…
Cancel
Save