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.
403 lines
9.5 KiB
403 lines
9.5 KiB
package main |
|
|
|
import ( |
|
"sort" |
|
"strings" |
|
"time" |
|
|
|
"code.rocketnine.space/tslocum/cview" |
|
"code.rocketnine.space/tslocum/fibs" |
|
"github.com/gdamore/tcell/v2" |
|
) |
|
|
|
const ( |
|
ScreenLobby = iota |
|
ScreenGame |
|
) |
|
|
|
var ( |
|
app *cview.Application |
|
uiGrid *cview.Grid |
|
userList *cview.List |
|
userListGrid *cview.Grid |
|
board *GameBoard |
|
actionBuffer *cview.TextView |
|
gameBuffer *cview.TextView |
|
statusBuffer *cview.TextView |
|
inputField *cview.InputField |
|
clock *cview.TextView |
|
form *cview.Form |
|
|
|
statusWriter *bufferWriter |
|
gameWriter *bufferWriter |
|
|
|
chatMode int |
|
viewScreen int |
|
screenX int |
|
) |
|
|
|
const ( |
|
ChatModeShout = iota |
|
ChatModeKibitz |
|
) |
|
|
|
const initialState = "bgammon:Welcome:5:0:2:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:-1:0:0:0:0:1:1:1:0:1:-1:0:25:0:0:0:0:4:0:0:0" |
|
|
|
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 updateChatType() { |
|
if chatMode == ChatModeShout { |
|
inputField.SetPlaceholder(" Shout") |
|
} else { |
|
inputField.SetPlaceholder(" Kibitz") |
|
} |
|
} |
|
|
|
func updateClock() { |
|
var displayedTime string |
|
var newTime string |
|
t := time.NewTicker(time.Second) |
|
for range t.C { |
|
newTime = time.Now().Format("15:04") |
|
if newTime != displayedTime { |
|
app.QueueUpdateDraw(func() { |
|
clock.SetText(newTime + string(cview.BoxDrawingsLightVertical)) |
|
}) |
|
displayedTime = newTime |
|
} |
|
} |
|
} |
|
|
|
func logIn(c *fibs.Client) { |
|
app.SetRoot(uiGrid, true) |
|
app.SetFocus(inputField) |
|
|
|
go c.Connect() |
|
} |
|
|
|
func setScreen(c *fibs.Client, screen int) { |
|
viewScreen = screen |
|
if viewScreen == ScreenLobby { |
|
updateUserList(c) |
|
board.SetVisible(false) |
|
userListGrid.SetVisible(true) |
|
} else { |
|
userListGrid.SetVisible(false) |
|
board.SetVisible(true) |
|
} |
|
buildLayout(c) |
|
} |
|
|
|
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].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) |
|
// TODO add list item |
|
_ = whoInfo |
|
} |
|
} |
|
|
|
func buildLayout(c *fibs.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 *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() { |
|
return true |
|
} |
|
|
|
if p != inputField { |
|
app.SetFocus(inputField) |
|
return false |
|
} |
|
|
|
return true |
|
} |
|
|
|
app.SetBeforeFocusFunc(focusFunc) |
|
|
|
actionBuffer = cview.NewTextView() |
|
|
|
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) |
|
|
|
clock = cview.NewTextView() |
|
clock.SetText(time.Now().Format("15:04") + string(cview.BoxDrawingsLightVertical)) |
|
// TODO what happens when the clock is clicked? |
|
|
|
inputField = cview.NewInputField() |
|
inputField.SetDoneFunc(func(key tcell.Key) { |
|
text := inputField.GetText() |
|
if len(text) == 0 { |
|
return |
|
} |
|
|
|
if text[0] == '/' { |
|
text = text[1:] |
|
} else if chatMode == ChatModeShout { |
|
text = "shout " + text |
|
} else if chatMode == ChatModeKibitz { |
|
text = "kibitz " + text |
|
} |
|
|
|
c.Out <- []byte(text) |
|
inputField.SetText("") |
|
}) |
|
inputField.SetFieldBackgroundColor(cview.Styles.PrimitiveBackgroundColor) |
|
inputField.SetFieldBackgroundColorFocused(cview.Styles.PrimitiveBackgroundColor) |
|
|
|
updateChatType() |
|
|
|
f3 := cview.NewFlex() |
|
f3.AddItem(board, 0, 1, false) |
|
f3.AddItem(gameBuffer, 0, 1, false) |
|
|
|
f4 := cview.NewFlex() |
|
f4.AddItem(clock, 7, 1, false) |
|
f4.AddItem(inputField, 0, 1, false) |
|
|
|
f := cview.NewFlex() |
|
f.SetDirection(cview.FlexRow) |
|
f.AddItem(f3, 14, 1, true) |
|
f.AddItem(actionBuffer, 3, 1, false) |
|
f.AddItem(statusBuffer, 0, 1, false) |
|
f.AddItem(f4, 1, 1, false) |
|
|
|
form = cview.NewForm() |
|
usernameField := cview.NewInputField() |
|
usernameField.SetLabel("Username") |
|
usernameField.SetFieldWidth(16) |
|
passwordField := cview.NewInputField() |
|
passwordField.SetLabel("Password") |
|
passwordField.SetFieldWidth(16) |
|
passwordField.SetMaskCharacter('*') |
|
form.AddFormItem(usernameField) |
|
form.AddFormItem(passwordField) |
|
|
|
connectFunc := func() { |
|
c.Username = usernameField.GetText() |
|
c.Password = passwordField.GetText() |
|
|
|
logIn(c) |
|
} |
|
|
|
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { |
|
if !c.LoggedIn() { |
|
if event.Key() == tcell.KeyEnter { |
|
formIndex, buttonIndex := form.GetFocusedItemIndex() |
|
if formIndex > 0 || buttonIndex == 0 { |
|
connectFunc() |
|
return nil |
|
} |
|
} |
|
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 |
|
} else { |
|
chatMode = ChatModeShout |
|
} |
|
updateChatType() |
|
return nil |
|
} |
|
return event |
|
}) |
|
|
|
form.AddButton("Connect", connectFunc) |
|
form.SetPadding(1, 0, 2, 0) |
|
|
|
logInHeader := cview.NewTextView() |
|
logInHeader.SetDynamicColors(true) |
|
logInHeader.SetText("\n [" + cview.ColorHex(cview.Styles.SecondaryTextColor) + "] Connect to FIBS[-]") |
|
|
|
logInFooter := cview.NewTextView() |
|
logInFooter.SetText(" For information on how to play backgammon, visit\n https://bkgm.com/rules.html\n\n For information on how to register, visit\n http://www.fibs.com/help.html#register\n\n For information on bgammon (this FIBS client), visit\n https://code.rocketnine.space/tslocum/bgammon") |
|
|
|
f2 := cview.NewFlex() // Login flex |
|
f2.SetDirection(cview.FlexRow) |
|
f2.AddItem(logInHeader, 2, 1, true) |
|
f2.AddItem(form, 8, 1, true) |
|
f2.AddItem(logInFooter, 0, 1, true) |
|
|
|
statusWriter = &bufferWriter{Buffer: statusBuffer} |
|
gameWriter = &bufferWriter{Buffer: gameBuffer} |
|
|
|
fibs.StatusWriter = statusWriter |
|
fibs.GameWriter = gameWriter |
|
|
|
userList = cview.NewList() |
|
userList.ShowSecondaryText(false) |
|
userList.SetHighlightFullLine(true) |
|
userList.AddContextItem("Invite", 'i', func(index int) { |
|
|
|
}) |
|
userList.AddContextItem("Watch", 'w', func(index int) { |
|
|
|
}) |
|
userList.AddContextItem("Message", 'm', func(index int) { |
|
|
|
}) |
|
userList.SetSelectedFunc(func(i int, item *cview.ListItem) { |
|
lf("%d", i) |
|
}) |
|
|
|
userListHeader := cview.NewTextView() |
|
userListHeader.SetText("[yellow::b]Player Rating Experience Status[-:-:-]") |
|
userListHeader.SetDynamicColors(true) |
|
|
|
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 = cview.NewGrid() |
|
|
|
app.SetAfterResizeFunc(func(width int, height int) { |
|
screenX = width |
|
buildLayout(c) |
|
}) |
|
|
|
// TODO refactor |
|
buildLayout(c) |
|
defer func() { |
|
if c.Username != "" && c.Password != "" { |
|
app.SetRoot(uiGrid, true) |
|
app.SetFocus(inputField) |
|
} |
|
}() |
|
|
|
go HandleEvents(c, b) |
|
|
|
setScreen(c, ScreenGame) |
|
|
|
lg("+---------------------------------------------------+") |
|
lg("| |") |
|
lg("| Welcome to bgammon. |") |
|
lg("| This program is free software. |") |
|
lg("| https://code.rocketnine.space/tslocum/bgammon |") |
|
lg("| |") |
|
lg("| |") |
|
lg("+---------------------------------------------------+") |
|
|
|
if c.Username == "" || c.Password == "" { |
|
app.SetRoot(f2, true) |
|
app.SetFocus(form) |
|
} else { |
|
logIn(c) |
|
} |
|
|
|
go updateClock() |
|
|
|
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() |
|
} |
|
} |
|
}
|
|
|