783 lines
21 KiB
Go
783 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"code.rocket9labs.com/tslocum/bgammon"
|
|
"code.rocketnine.space/tslocum/cview"
|
|
"github.com/gdamore/tcell/v2"
|
|
)
|
|
|
|
const (
|
|
ScreenLobby = iota
|
|
ScreenGame
|
|
)
|
|
|
|
var colorYellow = "#FFFF00"
|
|
|
|
var (
|
|
app *cview.Application
|
|
uiGrid *cview.Grid
|
|
gameList *cview.List
|
|
gameListFooter *cview.TextView
|
|
gameListGrid *cview.Grid
|
|
confirmExit *cview.TextView
|
|
board *GameBoard
|
|
boardGrid *cview.Grid
|
|
gameBuffer *cview.TextView
|
|
statusBuffer *cview.TextView
|
|
inputField *cview.InputField
|
|
loginForm *cview.Form
|
|
|
|
createGameForm *cview.Form
|
|
createGamePointsField *cview.InputField
|
|
createGamePasswordField *cview.InputField
|
|
createGameGrid *cview.Grid
|
|
|
|
joinGameForm *cview.Form
|
|
joinGameLabelField *cview.TextView
|
|
joinGamePasswordField *cview.InputField
|
|
joinGameGrid *cview.Grid
|
|
|
|
statusWriter *bufferWriter
|
|
gameWriter *bufferWriter
|
|
|
|
viewScreen int
|
|
inputMode bool
|
|
screenWidth int
|
|
|
|
showJoinGameDialog bool
|
|
joinGameID int
|
|
joinGameName string
|
|
|
|
allGames []bgammon.GameListing
|
|
autoRefresh = true
|
|
showCreateGameDialog bool
|
|
gameInProgress bool
|
|
|
|
spacerBox = cview.NewBox()
|
|
)
|
|
|
|
const loginFooterText = `To log in as a guest, enter a username (if you want) and
|
|
click connect, without entering a password.
|
|
|
|
For information on how to play backgammon, visit:
|
|
https://bkgm.com/rules.html
|
|
|
|
For information on bgammon-cli (this client), visit:
|
|
https://code.rocket9labs.com/tslocum/bgammon-cli`
|
|
|
|
type bufferWriter struct {
|
|
Buffer *cview.TextView
|
|
}
|
|
|
|
func (w *bufferWriter) Write(p []byte) (int, error) {
|
|
n, err := w.Buffer.Write(p)
|
|
app.Draw(w.Buffer)
|
|
return n, err
|
|
}
|
|
|
|
func logIn(c *Client) {
|
|
if c.connecting {
|
|
return
|
|
}
|
|
c.connecting = true
|
|
|
|
app.SetRoot(uiGrid, true)
|
|
updateFocus()
|
|
hideCursor()
|
|
|
|
l("*** Connecting...")
|
|
go c.Connect()
|
|
}
|
|
|
|
func setScreen(screen int) {
|
|
viewScreen = screen
|
|
buildLayout()
|
|
}
|
|
|
|
func primitiveInForm(p cview.Primitive, form *cview.Form) bool {
|
|
if p == form {
|
|
return true
|
|
}
|
|
for i := 0; i < form.GetFormItemCount(); i++ {
|
|
if p == form.GetFormItem(i) {
|
|
return true
|
|
}
|
|
}
|
|
for i := 0; i < form.GetButtonCount(); i++ {
|
|
if p == form.GetButton(i) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func beforeFocus(p cview.Primitive) bool {
|
|
if !board.client.LoggedIn() {
|
|
return primitiveInForm(p, loginForm)
|
|
}
|
|
|
|
if inputMode {
|
|
return p == inputField
|
|
}
|
|
|
|
if viewScreen == ScreenLobby {
|
|
if showCreateGameDialog || showJoinGameDialog {
|
|
return p != gameList && p != gameBuffer && p != statusBuffer && p != inputField
|
|
} else if gameInProgress {
|
|
return p == inputField
|
|
} else {
|
|
return p == gameList
|
|
}
|
|
} else {
|
|
return p == inputField
|
|
}
|
|
}
|
|
|
|
func hideCursor() {
|
|
screen := app.GetScreen()
|
|
if screen != nil {
|
|
screen.HideCursor()
|
|
}
|
|
}
|
|
|
|
func updateFocus() {
|
|
if viewScreen == ScreenLobby {
|
|
if inputMode {
|
|
app.SetFocus(inputField)
|
|
} else if showCreateGameDialog {
|
|
createGameForm.SetFocus(0)
|
|
app.SetFocus(createGameForm)
|
|
} else if showJoinGameDialog {
|
|
joinGameForm.SetFocus(0)
|
|
app.SetFocus(joinGameForm)
|
|
} else if gameInProgress {
|
|
app.SetFocus(inputField)
|
|
} else {
|
|
app.SetFocus(gameList)
|
|
}
|
|
} else {
|
|
if inputMode {
|
|
app.SetFocus(inputField)
|
|
} else {
|
|
app.SetFocus(gameBuffer)
|
|
hideCursor()
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildLayout() {
|
|
autoRefreshStatus := "enabled"
|
|
if !autoRefresh {
|
|
autoRefreshStatus = "disabled"
|
|
}
|
|
gameListFooter.SetText("[" + colorYellow + "][\"btncreate\"][ Create match ][\"\"] [\"btnrefresh\"][ Refresh ][\"\"][-:-:-][\"dummy\"] [" + colorYellow + "][\"btnautorefresh\"][ Auto-refresh " + autoRefreshStatus + " ][\"\"][-:-:-][\"dummy\"] [\"\"]")
|
|
|
|
uiGrid.Clear()
|
|
|
|
var currentScreen cview.Primitive
|
|
if viewScreen == ScreenLobby {
|
|
if showCreateGameDialog {
|
|
currentScreen = createGameGrid
|
|
} else if showJoinGameDialog {
|
|
currentScreen = joinGameGrid
|
|
} else if gameInProgress {
|
|
currentScreen = confirmExit
|
|
} else {
|
|
currentScreen = gameListGrid
|
|
}
|
|
} else {
|
|
currentScreen = boardGrid
|
|
}
|
|
updateFocus()
|
|
|
|
var bottomField cview.Primitive
|
|
if inputMode {
|
|
bottomField = inputField
|
|
} else {
|
|
bottomField = spacerBox
|
|
}
|
|
|
|
const boardHeight = 16
|
|
|
|
// Single column on smaller screens
|
|
const dualColumnWidth = 156
|
|
if screenWidth < dualColumnWidth {
|
|
uiGrid.SetRows(boardHeight, 1, -1, 1, -1, 1)
|
|
uiGrid.SetColumns(-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(bottomField, 5, 0, 1, 1, 0, 0, true)
|
|
return
|
|
}
|
|
|
|
uiGrid.SetRows(boardHeight, 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(bottomField, 3, 0, 1, 3, 0, 0, true)
|
|
}
|
|
|
|
func resetCreateGameDialog() {
|
|
nameInput := createGameForm.GetFormItem(0).(*cview.InputField)
|
|
pointsInput := createGameForm.GetFormItem(1).(*cview.InputField)
|
|
typeDropDown := createGameForm.GetFormItem(2).(*cview.DropDown)
|
|
passwordInput := createGameForm.GetFormItem(3).(*cview.InputField)
|
|
|
|
nameInput.SetText("")
|
|
pointsInput.SetText("1")
|
|
typeDropDown.SetCurrentOption(0)
|
|
passwordInput.SetText("")
|
|
passwordInput.SetVisible(false)
|
|
}
|
|
|
|
func acceptCreateGameDialog() {
|
|
if viewScreen == ScreenGame {
|
|
return
|
|
}
|
|
|
|
nameInput := createGameForm.GetFormItem(0).(*cview.InputField)
|
|
pointsInput := createGameForm.GetFormItem(1).(*cview.InputField)
|
|
typeDropDown := createGameForm.GetFormItem(2).(*cview.DropDown)
|
|
passwordInput := createGameForm.GetFormItem(3).(*cview.InputField)
|
|
|
|
typeAndPassword := "public"
|
|
index, _ := typeDropDown.GetCurrentOption()
|
|
if index == 1 {
|
|
if strings.TrimSpace(passwordInput.GetText()) == "" {
|
|
createGameForm.SetFocus(3)
|
|
app.SetFocus(createGameForm)
|
|
l("Please enter a password to create a private match.")
|
|
return
|
|
}
|
|
typeAndPassword = fmt.Sprintf("private %s", strings.ReplaceAll(passwordInput.GetText(), " ", "_"))
|
|
}
|
|
|
|
points, err := strconv.Atoi(pointsInput.GetText())
|
|
if err != nil {
|
|
points = 1
|
|
}
|
|
|
|
board.client.Out <- []byte(fmt.Sprintf("c %s %d %s", typeAndPassword, points, nameInput.GetText()))
|
|
}
|
|
|
|
func RunApp(c *Client, b *GameBoard) error {
|
|
app.EnableMouse(true)
|
|
|
|
app.SetBeforeFocusFunc(beforeFocus)
|
|
|
|
gameBuffer = cview.NewTextView()
|
|
gameBuffer.SetVerticalAlign(cview.AlignBottom)
|
|
gameBuffer.SetScrollable(true)
|
|
gameBuffer.SetScrollBarVisibility(cview.ScrollBarAlways)
|
|
|
|
statusBuffer = cview.NewTextView()
|
|
statusBuffer.SetVerticalAlign(cview.AlignBottom)
|
|
statusBuffer.SetScrollable(true)
|
|
statusBuffer.SetScrollBarVisibility(cview.ScrollBarAlways)
|
|
|
|
confirmExit = cview.NewTextView()
|
|
confirmExit.SetText("Are you sure you want to leave the match?\n\n(Press Y/N)")
|
|
confirmExit.SetTextAlign(cview.AlignCenter)
|
|
confirmExit.SetVerticalAlign(cview.AlignMiddle)
|
|
|
|
inputField = cview.NewInputField()
|
|
inputField.SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
|
|
if inputMode {
|
|
return true
|
|
}
|
|
|
|
if viewScreen == ScreenLobby {
|
|
if showCreateGameDialog {
|
|
acceptCreateGameDialog()
|
|
} else if gameInProgress {
|
|
if lastChar == 'y' || lastChar == 'Y' {
|
|
board.client.Out <- []byte("leave")
|
|
} else if lastChar == 'n' || lastChar == 'N' {
|
|
setScreen(ScreenGame)
|
|
}
|
|
}
|
|
return false
|
|
} else if !inputMode {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
inputField.SetFieldBackgroundColor(cview.Styles.PrimitiveBackgroundColor)
|
|
inputField.SetFieldBackgroundColorFocused(cview.Styles.PrimitiveBackgroundColor)
|
|
|
|
boardGrid = cview.NewGrid()
|
|
boardGrid.SetColumns(2, -1)
|
|
boardGrid.SetRows(1, 15)
|
|
boardGrid.AddItem(cview.NewBox(), 0, 0, 1, 2, 0, 0, false)
|
|
boardGrid.AddItem(cview.NewBox(), 1, 0, 1, 1, 0, 0, false)
|
|
boardGrid.AddItem(board, 1, 1, 1, 1, 0, 0, true)
|
|
|
|
loginForm = cview.NewForm()
|
|
usernameField := cview.NewInputField()
|
|
usernameField.SetLabel("Username")
|
|
usernameField.SetFieldWidth(16)
|
|
usernameField.SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
|
|
return !unicode.IsSpace(lastChar)
|
|
})
|
|
loginPasswordField := cview.NewInputField()
|
|
loginPasswordField.SetLabel("Password")
|
|
loginPasswordField.SetFieldWidth(16)
|
|
loginPasswordField.SetMaskCharacter('*')
|
|
loginForm.AddFormItem(usernameField)
|
|
loginForm.AddFormItem(loginPasswordField)
|
|
|
|
connectFunc := func() {
|
|
c.Username = usernameField.GetText()
|
|
c.Password = loginPasswordField.GetText()
|
|
|
|
logIn(c)
|
|
}
|
|
|
|
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
handleCreateGameInput := viewScreen == ScreenLobby && showCreateGameDialog
|
|
handleJoinGameInput := viewScreen == ScreenLobby && showJoinGameDialog
|
|
handleGameInput := viewScreen == ScreenGame && !inputMode && gameInProgress
|
|
|
|
switch event.Key() {
|
|
case tcell.KeyEnter:
|
|
if !c.LoggedIn() {
|
|
connectFunc()
|
|
return nil
|
|
} else if inputMode {
|
|
text := inputField.GetText()
|
|
if len(text) == 0 {
|
|
inputMode = false
|
|
buildLayout()
|
|
return nil
|
|
}
|
|
|
|
if text[0] == '/' {
|
|
text = text[1:]
|
|
} else {
|
|
l(fmt.Sprintf("<%s> %s", c.Username, text))
|
|
text = "say " + text
|
|
}
|
|
|
|
c.Out <- []byte(text)
|
|
inputField.SetText("")
|
|
return nil
|
|
} else if handleCreateGameInput {
|
|
if app.GetFocus() == createGameForm.GetButton(0) {
|
|
showCreateGameDialog = false
|
|
buildLayout()
|
|
} else {
|
|
acceptCreateGameDialog()
|
|
}
|
|
return nil
|
|
} else if handleJoinGameInput {
|
|
if app.GetFocus() == joinGameForm.GetButton(0) {
|
|
showJoinGameDialog = false
|
|
buildLayout()
|
|
} else {
|
|
board.client.Out <- []byte(fmt.Sprintf("j %d %s", joinGameID, strings.ReplaceAll(joinGamePasswordField.GetText(), " ", "_")))
|
|
}
|
|
return nil
|
|
} else if handleGameInput {
|
|
inputMode = true
|
|
buildLayout()
|
|
return nil
|
|
} else {
|
|
selected := gameList.GetCurrentItemIndex()
|
|
if viewScreen == ScreenLobby && len(allGames) > 0 && selected >= 0 && selected < len(allGames) {
|
|
if allGames[selected].Password {
|
|
showJoinGameDialog = true
|
|
joinGameID = allGames[selected].ID
|
|
buildLayout()
|
|
} else {
|
|
board.client.Out <- []byte(fmt.Sprintf("j %d", allGames[selected].ID))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
case tcell.KeyESC:
|
|
if viewScreen == ScreenLobby {
|
|
if showCreateGameDialog {
|
|
showCreateGameDialog = false
|
|
buildLayout()
|
|
return nil
|
|
} else if showJoinGameDialog {
|
|
showJoinGameDialog = false
|
|
buildLayout()
|
|
return nil
|
|
} else if !gameInProgress {
|
|
return nil
|
|
}
|
|
} else if inputMode {
|
|
inputMode = false
|
|
buildLayout()
|
|
return nil
|
|
}
|
|
newScreen := ScreenLobby
|
|
if viewScreen == ScreenLobby {
|
|
newScreen = ScreenGame
|
|
}
|
|
setScreen(newScreen)
|
|
return nil
|
|
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
|
if inputMode {
|
|
return event
|
|
}
|
|
|
|
// Undo move.
|
|
if handleGameInput && len(board.Board.Moves) > 0 {
|
|
lastMove := board.Board.Moves[len(board.Board.Moves)-1]
|
|
board.client.Out <- []byte(fmt.Sprintf("mv %d/%d", lastMove[1], lastMove[0]))
|
|
return nil
|
|
}
|
|
case tcell.KeyRune:
|
|
if inputMode {
|
|
return event
|
|
} else if event.Rune() == '/' {
|
|
inputMode = true
|
|
if len(strings.TrimSpace(inputField.GetText())) == 0 {
|
|
inputField.SetText("/")
|
|
}
|
|
buildLayout()
|
|
return nil
|
|
}
|
|
|
|
if viewScreen == ScreenGame && !inputMode {
|
|
if gameInProgress {
|
|
switch event.Rune() {
|
|
case 'r', 'R':
|
|
if board.Board.MayRoll() {
|
|
board.client.Out <- []byte("roll")
|
|
}
|
|
case 'k', 'K':
|
|
if board.Board.MayOK() {
|
|
board.client.Out <- []byte("ok")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return event
|
|
})
|
|
|
|
loginForm.AddButton("Connect", connectFunc)
|
|
loginForm.SetPadding(1, 0, 2, 0)
|
|
|
|
logInHeader := cview.NewTextView()
|
|
logInHeader.SetDynamicColors(true)
|
|
logInHeader.SetText("[" + cview.ColorHex(cview.Styles.SecondaryTextColor) + "]Connect to bgammon.org[-]")
|
|
|
|
logInFooter := cview.NewTextView()
|
|
logInFooter.SetText(loginFooterText)
|
|
|
|
f2 := cview.NewFlex() // Login flex
|
|
f2.SetDirection(cview.FlexRow)
|
|
f2.AddItem(logInHeader, 1, 1, true)
|
|
f2.AddItem(loginForm, 8, 1, true)
|
|
f2.AddItem(logInFooter, 0, 1, true)
|
|
|
|
statusWriter = &bufferWriter{Buffer: statusBuffer}
|
|
gameWriter = &bufferWriter{Buffer: gameBuffer}
|
|
|
|
gameList = cview.NewList()
|
|
gameList.ShowSecondaryText(false)
|
|
gameList.SetHighlightFullLine(true)
|
|
gameList.AddContextItem("Join", 'j', func(index int) {
|
|
|
|
})
|
|
gameList.SetSelectedFunc(func(i int, item *cview.ListItem) {
|
|
if i < 0 || i >= len(allGames) {
|
|
return
|
|
}
|
|
// TODO prompt for password when required
|
|
entry := allGames[i]
|
|
if entry.Password {
|
|
showJoinGameDialog = true
|
|
joinGameID = entry.ID
|
|
buildLayout()
|
|
} else {
|
|
board.client.Out <- []byte(fmt.Sprintf("j %d", entry.ID))
|
|
}
|
|
})
|
|
|
|
gameListHeader := cview.NewTextView()
|
|
gameListHeader.SetText("[" + colorYellow + "]Status Points Name[-:-:-]")
|
|
gameListHeader.SetDynamicColors(true)
|
|
|
|
gameListFooter = cview.NewTextView()
|
|
gameListFooter.SetDynamicColors(true)
|
|
gameListFooter.SetRegions(true)
|
|
gameListFooter.SetHighlightedFunc(func(added, removed, remaining []string) {
|
|
defer gameListFooter.Highlight()
|
|
if len(added) > 0 {
|
|
switch added[0] {
|
|
case "btncreate":
|
|
showCreateGameDialog = true
|
|
resetCreateGameDialog()
|
|
buildLayout()
|
|
case "btnrefresh":
|
|
board.client.Out <- []byte("ls")
|
|
case "btnautorefresh":
|
|
autoRefresh = !autoRefresh
|
|
buildLayout()
|
|
}
|
|
}
|
|
})
|
|
|
|
createGameLabel := cview.NewTextView()
|
|
createGameLabel.SetDynamicColors(true)
|
|
createGameLabel.SetText("[" + colorYellow + "]Create match[-]")
|
|
|
|
publicOption := cview.NewDropDownOption("Public")
|
|
publicOption.SetSelectedFunc(func(index int, option *cview.DropDownOption) {
|
|
if createGamePasswordField == nil {
|
|
return
|
|
}
|
|
createGamePasswordField.SetVisible(false)
|
|
})
|
|
|
|
privateOption := cview.NewDropDownOption("Private")
|
|
privateOption.SetSelectedFunc(func(index int, option *cview.DropDownOption) {
|
|
if createGamePasswordField == nil {
|
|
return
|
|
}
|
|
createGamePasswordField.SetVisible(true)
|
|
})
|
|
|
|
createGameForm = cview.NewForm()
|
|
createGameForm.AddInputField("Name", "", 10, nil, nil)
|
|
createGameForm.AddInputField("Points", "", 10, func(textToCheck string, lastChar rune) bool {
|
|
allowed := []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
|
for _, allowedRune := range allowed {
|
|
if lastChar == allowedRune {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}, nil)
|
|
createGameForm.AddDropDown("Type", 0, nil, []*cview.DropDownOption{publicOption, privateOption})
|
|
createGameForm.AddPasswordField("Password", "", 10, '*', nil)
|
|
createGameForm.AddButton("Cancel", func() {
|
|
showCreateGameDialog = false
|
|
buildLayout()
|
|
})
|
|
createGameForm.AddButton("Create", func() {
|
|
acceptCreateGameDialog()
|
|
})
|
|
createGamePointsField = createGameForm.GetFormItem(1).(*cview.InputField)
|
|
createGamePasswordField = createGameForm.GetFormItem(3).(*cview.InputField)
|
|
resetCreateGameDialog()
|
|
|
|
createGameGrid = cview.NewGrid()
|
|
createGameGrid.SetRows(1, -1)
|
|
createGameGrid.SetColumns(1, -1)
|
|
createGameGrid.AddItem(createGameLabel, 0, 0, 1, 2, 0, 0, false)
|
|
createGameGrid.AddItem(cview.NewBox(), 1, 0, 1, 1, 0, 0, true)
|
|
createGameGrid.AddItem(createGameForm, 1, 1, 1, 1, 0, 0, true)
|
|
|
|
joinGameLabelField = cview.NewTextView()
|
|
joinGameLabelField.SetDynamicColors(true)
|
|
joinGameLabelField.SetText("[" + colorYellow + "]Join match: " + cview.Escape(joinGameName) + "[-]")
|
|
|
|
joinGameForm = cview.NewForm()
|
|
joinGameForm.AddPasswordField("Password", "", 10, '*', nil)
|
|
joinGameForm.AddButton("Cancel", func() {
|
|
showJoinGameDialog = false
|
|
buildLayout()
|
|
})
|
|
joinGameForm.AddButton("Join", func() {
|
|
board.client.Out <- []byte(fmt.Sprintf("j %d %s", joinGameID, strings.ReplaceAll(joinGamePasswordField.GetText(), " ", "_")))
|
|
})
|
|
joinGamePasswordField = joinGameForm.GetFormItem(0).(*cview.InputField)
|
|
|
|
joinGameGrid = cview.NewGrid()
|
|
joinGameGrid.SetRows(1, -1)
|
|
joinGameGrid.SetColumns(1, -1)
|
|
joinGameGrid.AddItem(joinGameLabelField, 0, 0, 1, 2, 0, 0, false)
|
|
joinGameGrid.AddItem(cview.NewBox(), 1, 0, 1, 1, 0, 0, true)
|
|
joinGameGrid.AddItem(joinGameForm, 1, 1, 1, 1, 0, 0, true)
|
|
|
|
gameListGrid = cview.NewGrid()
|
|
gameListGrid.SetRows(1, -1, 1)
|
|
gameListGrid.AddItem(gameListHeader, 0, 0, 1, 1, 0, 0, false)
|
|
gameListGrid.AddItem(gameList, 1, 0, 1, 1, 0, 0, true)
|
|
gameListGrid.AddItem(gameListFooter, 2, 0, 1, 1, 0, 0, false)
|
|
|
|
uiGrid = cview.NewGrid()
|
|
|
|
app.SetAfterResizeFunc(func(width int, height int) {
|
|
screenWidth = width
|
|
buildLayout()
|
|
})
|
|
|
|
buildLayout()
|
|
defer func() {
|
|
if c.Username != "" {
|
|
app.SetRoot(uiGrid, true)
|
|
app.SetFocus(inputField)
|
|
}
|
|
}()
|
|
|
|
go HandleEvents(c, b)
|
|
|
|
lg("This client and the bgammon.org server are free and open source:")
|
|
lg("- https://code.rocket9labs.com/tslocum/bgammon")
|
|
lg("- https://code.rocket9labs.com/tslocum/bgammon-cli")
|
|
lg("Press <Enter> to enable text input.")
|
|
|
|
if c.Username == "" {
|
|
app.SetRoot(f2, true)
|
|
app.SetFocus(loginForm)
|
|
} else {
|
|
logIn(c)
|
|
}
|
|
|
|
return app.Run()
|
|
}
|
|
|
|
func UpdateGameList(ev *bgammon.EventList) {
|
|
allGames = make([]bgammon.GameListing, len(ev.Games))
|
|
copy(allGames, ev.Games)
|
|
|
|
gameList.Clear()
|
|
|
|
if len(ev.Games) == 0 {
|
|
gameList.AddItem(cview.NewListItem("*** No matches available. Please create one. ***"))
|
|
} else {
|
|
var entryStatus string
|
|
for _, entry := range ev.Games {
|
|
if entry.Players == 2 {
|
|
entryStatus = "Full"
|
|
} else {
|
|
if !entry.Password {
|
|
entryStatus = "Open"
|
|
} else {
|
|
entryStatus = "Private"
|
|
}
|
|
}
|
|
gameList.AddItem(cview.NewListItem(fmt.Sprintf("%-7s %-7s %-30s", entryStatus, strconv.Itoa(int(entry.Points)), entry.Name)))
|
|
}
|
|
}
|
|
|
|
if viewScreen == ScreenLobby && !showCreateGameDialog && !showJoinGameDialog {
|
|
app.Draw()
|
|
}
|
|
}
|
|
|
|
func HandleEvents(c *Client, b *GameBoard) {
|
|
for e := range c.Events {
|
|
switch ev := e.(type) {
|
|
case *bgammon.EventWelcome:
|
|
c.Username = ev.PlayerName
|
|
areIs := "are"
|
|
if ev.Clients == 1 {
|
|
areIs = "is"
|
|
}
|
|
clientsPlural := "s"
|
|
if ev.Clients == 1 {
|
|
clientsPlural = ""
|
|
}
|
|
matchesPlural := "es"
|
|
if ev.Games == 1 {
|
|
matchesPlural = ""
|
|
}
|
|
l(fmt.Sprintf("*** Welcome, %s. There %s %d client%s playing %d match%s.", ev.PlayerName, areIs, ev.Clients, clientsPlural, ev.Games, matchesPlural))
|
|
case *bgammon.EventHelp:
|
|
l(fmt.Sprintf("Help: %s", ev.Message))
|
|
case *bgammon.EventPing:
|
|
c.Out <- []byte(fmt.Sprintf("pong %s", ev.Message))
|
|
case *bgammon.EventNotice:
|
|
l(fmt.Sprintf("*** %s", ev.Message))
|
|
case *bgammon.EventSay:
|
|
l(fmt.Sprintf("<%s> %s", ev.Player, ev.Message))
|
|
case *bgammon.EventList:
|
|
UpdateGameList(ev)
|
|
case *bgammon.EventJoined:
|
|
if ev.PlayerNumber == 1 {
|
|
b.Board.Player1.Name = ev.Player
|
|
} else if ev.PlayerNumber == 2 {
|
|
b.Board.Player2.Name = ev.Player
|
|
}
|
|
b.Update()
|
|
|
|
gameInProgress = true
|
|
showCreateGameDialog = false
|
|
showJoinGameDialog = false
|
|
setScreen(ScreenGame)
|
|
|
|
if ev.Player == c.Username {
|
|
gameBuffer.SetText("")
|
|
gameLogged = false
|
|
} else {
|
|
lg(fmt.Sprintf("%s joined the match.", ev.Player))
|
|
}
|
|
case *bgammon.EventFailedJoin:
|
|
l(fmt.Sprintf("*** Failed to join match: %s", ev.Reason))
|
|
case *bgammon.EventLeft:
|
|
if b.Board.Player1.Name == ev.Player {
|
|
b.Board.Player1.Name = ""
|
|
} else if b.Board.Player2.Name == ev.Player {
|
|
b.Board.Player2.Name = ""
|
|
}
|
|
b.Update()
|
|
if ev.Player == c.Username {
|
|
gameInProgress = false
|
|
setScreen(ScreenLobby)
|
|
} else {
|
|
lg(fmt.Sprintf("%s left the match.", ev.Player))
|
|
}
|
|
case *bgammon.EventBoard:
|
|
b.Board = &ev.GameState
|
|
b.Update()
|
|
case *bgammon.EventRolled:
|
|
b.Board.Roll1 = ev.Roll1
|
|
b.Board.Roll2 = ev.Roll2
|
|
var diceFormatted string
|
|
if b.Board.Turn == 0 {
|
|
if b.Board.Player1.Name == ev.Player {
|
|
diceFormatted = fmt.Sprintf("%d", b.Board.Roll1)
|
|
} else {
|
|
diceFormatted = fmt.Sprintf("%d", b.Board.Roll2)
|
|
}
|
|
} else {
|
|
diceFormatted = fmt.Sprintf("%d-%d", b.Board.Roll1, b.Board.Roll2)
|
|
}
|
|
b.Update()
|
|
lg(fmt.Sprintf("%s rolled %s.", ev.Player, diceFormatted))
|
|
case *bgammon.EventFailedRoll:
|
|
l(fmt.Sprintf("*** Failed to roll: %s", ev.Reason))
|
|
case *bgammon.EventMoved:
|
|
b.Update()
|
|
lg(fmt.Sprintf("%s moved %s.", ev.Player, bgammon.FormatMoves(ev.Moves)))
|
|
case *bgammon.EventFailedMove:
|
|
c.Out <- []byte("board") // Refresh game state.
|
|
|
|
var extra string
|
|
if ev.From != 0 || ev.To != 0 {
|
|
extra = fmt.Sprintf(" from %s to %s", bgammon.FormatSpace(ev.From), bgammon.FormatSpace(ev.To))
|
|
}
|
|
l(fmt.Sprintf("*** Failed to move checker%s: %s", extra, ev.Reason))
|
|
l(fmt.Sprintf("*** Legal moves: %s", bgammon.FormatMoves(b.Board.Available)))
|
|
case *bgammon.EventFailedOk:
|
|
c.Out <- []byte("board") // Refresh game state.
|
|
l(fmt.Sprintf("*** Failed to submit moves: %s", ev.Reason))
|
|
case *bgammon.EventWin:
|
|
lg(fmt.Sprintf("%s wins!", ev.Player))
|
|
case *bgammon.EventSettings:
|
|
// Do nothing.
|
|
default:
|
|
l(fmt.Sprintf("*** WARNING: You may need to upgrade your client. Unknown event received: %+v %+v", ev, e))
|
|
}
|
|
}
|
|
}
|