328 lines
7.3 KiB
Go
328 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"gitlab.com/tslocum/cbind"
|
|
"gitlab.com/tslocum/cview"
|
|
"gitlab.com/tslocum/joker"
|
|
cribbage "gitlab.com/tslocum/joker-cribbage"
|
|
)
|
|
|
|
const (
|
|
defaultStatusText = "Press Enter to chat, 1-6 to throw a card, Space to continue"
|
|
logFormat = "2006-01-02 15:04:05"
|
|
)
|
|
|
|
var (
|
|
debug bool
|
|
|
|
app *cview.Application
|
|
inputConfig *cbind.Configuration
|
|
|
|
statusText *statusTextView
|
|
gameText *cview.TextView
|
|
|
|
inputField *cview.InputField
|
|
statusBuf *cview.TextView
|
|
|
|
statusGrid *cview.Grid
|
|
|
|
throwGrid *cview.Grid
|
|
starterWidget *cardWidget
|
|
mainHandStack *cardStackWidget
|
|
mainCribStack *cardStackWidget
|
|
throwStack *cardStackWidget
|
|
oppCribStack *cardStackWidget
|
|
|
|
currentGameState = &gameState{}
|
|
|
|
writeBuffer = make(chan string)
|
|
|
|
exit = make(chan bool)
|
|
|
|
inputFocused bool
|
|
|
|
wroteFirstLogMessage bool
|
|
logMutex sync.Mutex
|
|
)
|
|
|
|
func setupThrowGrid() {
|
|
if throwGrid == nil {
|
|
throwGrid = cview.NewGrid()
|
|
}
|
|
|
|
throwGrid.Clear()
|
|
|
|
if currentGameState.Phase == PhasePeg {
|
|
throwGrid.SetColumns(-1)
|
|
throwGrid.AddItem(throwStack, 0, 0, 1, 1, 0, 0, false)
|
|
} else {
|
|
throwGrid.SetColumns((cardBufferX * 5) + cardWidth)
|
|
|
|
throwGrid.AddItem(throwStack, 0, 0, 1, 1, 0, 0, false)
|
|
throwGrid.AddItem(oppCribStack, 0, 1, 1, 1, 0, 0, false)
|
|
}
|
|
}
|
|
|
|
// Starts at 1
|
|
func selectCardIndex(cardNumber int) {
|
|
if cardNumber <= 0 || cardNumber > len(mainHandStack.Cards) {
|
|
return
|
|
}
|
|
|
|
var selected int
|
|
if !mainHandStack.Cards[cardNumber-1].selected {
|
|
for _, card := range mainHandStack.Cards {
|
|
if card.selected {
|
|
selected++
|
|
}
|
|
}
|
|
if selected >= mainHandStack.GetMaxSelection() {
|
|
return
|
|
}
|
|
}
|
|
|
|
mainHandStack.Cards[cardNumber-1].Select()
|
|
}
|
|
|
|
func throwCard(card joker.Card) {
|
|
if currentGameState.Phase == PhasePick {
|
|
var selected int
|
|
for i := range mainHandStack.Cards {
|
|
if mainHandStack.Cards[i].selected {
|
|
selected++
|
|
}
|
|
}
|
|
|
|
if len(mainHandStack.Cards) == 6 {
|
|
if selected == 2 {
|
|
// TODO Race condition
|
|
for _, throwCard := range mainHandStack.Cards {
|
|
if throwCard.selected {
|
|
writeBuffer <- fmt.Sprintf("throw %s", throwCard.Identifier())
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
writeBuffer <- fmt.Sprintf("throw %s", card.Identifier())
|
|
}
|
|
} else if currentGameState.Phase == PhasePeg && currentGameState.Turn == currentGameState.Player {
|
|
if cribbage.Sum(currentGameState.ThrowPile.Cards())+cribbage.Value(card) > 31 {
|
|
setStatusText("Can not throw card: illegal move")
|
|
} else {
|
|
writeBuffer <- fmt.Sprintf("throw %s", card.Identifier())
|
|
}
|
|
}
|
|
}
|
|
|
|
func doAction() {
|
|
var cards joker.Cards
|
|
for _, card := range mainHandStack.Cards {
|
|
if card.selected {
|
|
cards = append(cards, card.Card)
|
|
}
|
|
}
|
|
|
|
if len(cards) > 0 {
|
|
for i := range cards {
|
|
throwCard(cards[i])
|
|
}
|
|
return
|
|
}
|
|
|
|
writeBuffer <- "continue"
|
|
}
|
|
|
|
func setStatusText(status string) {
|
|
app.QueueUpdateDraw(func() {
|
|
statusText.SetText(" " + status)
|
|
})
|
|
}
|
|
|
|
func updateGameText() {
|
|
playerScore := currentGameState.Score1
|
|
opponentScore := currentGameState.Score2
|
|
if currentGameState.Player == 2 {
|
|
playerScore = currentGameState.Score2
|
|
opponentScore = currentGameState.Score1
|
|
}
|
|
|
|
playerScorePrinted := fmt.Sprintf("%d", playerScore)
|
|
opponentScorePrinted := fmt.Sprintf("%d", opponentScore)
|
|
|
|
newGameText := "\n\n\n" + playerScorePrinted + strings.Repeat(" ", ((cardWidth-len(playerScorePrinted))-len(opponentScorePrinted))-2) + opponentScorePrinted
|
|
|
|
newGameText += "\n\nYou" + strings.Repeat(" ", cardWidth-8) + "Opp"
|
|
|
|
gameText.SetText(newGameText)
|
|
}
|
|
|
|
func toggleFocus() {
|
|
if inputFocused && inputField.GetText() != "" {
|
|
writeBuffer <- "msg " + inputField.GetText()
|
|
statusMessage(fmt.Sprintf("<%s> %s", "You", inputField.GetText()))
|
|
}
|
|
|
|
inputFocused = !inputFocused
|
|
inputField.SetText("")
|
|
focusUpdated()
|
|
}
|
|
|
|
func focusUpdated() {
|
|
if inputFocused {
|
|
app.SetFocus(inputField)
|
|
} else {
|
|
app.SetFocus(nil)
|
|
}
|
|
}
|
|
|
|
func statusMessage(message string) {
|
|
logMutex.Lock()
|
|
defer logMutex.Unlock()
|
|
|
|
var prefix string
|
|
if !wroteFirstLogMessage {
|
|
wroteFirstLogMessage = true
|
|
} else {
|
|
prefix = "\n"
|
|
}
|
|
|
|
if len(message) > 0 && message[0:1] != "<" {
|
|
message = "* " + message
|
|
}
|
|
|
|
statusBuf.Write([]byte(prefix + time.Now().Format(logFormat) + " " + message))
|
|
|
|
app.Draw()
|
|
}
|
|
|
|
func main() {
|
|
cview.Styles.PrimaryTextColor = tcell.ColorDefault
|
|
cview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault
|
|
|
|
var connectAddress string
|
|
flag.BoolVar(&debug, "debug", false, "print debug information")
|
|
flag.StringVar(&connectAddress, "server", "wss://play.cribbage.world/crib", "Server address")
|
|
flag.Parse()
|
|
|
|
app = cview.NewApplication().EnableMouse(true)
|
|
|
|
inputConfig = cbind.NewConfiguration()
|
|
|
|
inputConfig.SetKey(tcell.ModNone, tcell.KeyEnter, func(ev *tcell.EventKey) *tcell.EventKey {
|
|
toggleFocus()
|
|
return nil
|
|
})
|
|
|
|
inputConfig.SetRune(tcell.ModNone, ' ', func(ev *tcell.EventKey) *tcell.EventKey {
|
|
if inputFocused {
|
|
return ev
|
|
}
|
|
|
|
doAction()
|
|
return nil
|
|
})
|
|
|
|
for i := 1; i <= 6; i++ {
|
|
i := i // Capture
|
|
inputConfig.SetRune(tcell.ModNone, '0'+rune(i), func(ev *tcell.EventKey) *tcell.EventKey {
|
|
if inputFocused {
|
|
return ev
|
|
}
|
|
|
|
selectCardIndex(i)
|
|
app.Draw()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
app.SetInputCapture(inputConfig.Capture)
|
|
|
|
starterWidget = newCardWidget(joker.Card{}, nil)
|
|
mainHandStack = newCardStackWidget()
|
|
mainCribStack = newCardStackWidget()
|
|
throwStack = newCardStackWidget()
|
|
oppCribStack = newCardStackWidget()
|
|
|
|
mainHandStack.EverSelectable = true
|
|
mainCribStack.EverSelectable = true
|
|
|
|
mainHandStack.SetMaxSelection(2)
|
|
mainCribStack.SetMaxSelection(-1)
|
|
throwStack.SetMaxSelection(-1)
|
|
oppCribStack.SetMaxSelection(-1)
|
|
|
|
g := cview.NewGrid().
|
|
SetColumns(cardWidth, 1, (cardBufferX*5)+cardWidth, 2, -1).
|
|
SetRows(cardHeight, 1, 1, cardHeight+1, 2, -1)
|
|
|
|
gameText = cview.NewTextView()
|
|
gameText.SetTextAlign(cview.AlignCenter)
|
|
|
|
statusGrid = cview.NewGrid().
|
|
SetColumns(1, -1, 1).
|
|
SetRows(1, 1, -1)
|
|
statusGrid.AddItem(cview.NewTextView(), 0, 0, 3, 1, 0, 0, false)
|
|
statusGrid.AddItem(cview.NewTextView(), 0, 1, 1, 2, 0, 0, false)
|
|
statusGrid.AddItem(cview.NewTextView(), 1, 0, 1, 1, 0, 0, false)
|
|
statusGrid.AddItem(gameText, 2, 1, 1, 1, 0, 0, false)
|
|
|
|
statusText = newStatusTextView()
|
|
|
|
inputField = cview.NewInputField().
|
|
SetText("").
|
|
SetLabel("> ").
|
|
SetFieldWidth(0).
|
|
SetFieldBackgroundColor(tcell.ColorDefault).
|
|
SetFieldTextColor(tcell.ColorDefault)
|
|
|
|
statusBuf = cview.NewTextView().SetWrap(true).SetWordWrap(true)
|
|
|
|
setupThrowGrid()
|
|
|
|
g.AddItem(starterWidget, 0, 0, 1, 1, 0, 0, false)
|
|
g.AddItem(cview.NewTextView(), 0, 1, 1, 1, 0, 0, false)
|
|
g.AddItem(throwGrid, 0, 2, 1, 3, 0, 0, false)
|
|
|
|
g.AddItem(cview.NewTextView(), 1, 0, 1, 5, 0, 0, false)
|
|
|
|
g.AddItem(statusText, 2, 0, 1, 5, 0, 0, false)
|
|
|
|
g.AddItem(statusGrid, 3, 0, 1, 1, 0, 0, false)
|
|
g.AddItem(cview.NewTextView(), 3, 1, 1, 1, 0, 0, false)
|
|
g.AddItem(mainHandStack, 3, 2, 1, 1, 0, 0, false)
|
|
g.AddItem(cview.NewTextView(), 3, 3, 1, 1, 0, 0, false)
|
|
g.AddItem(mainCribStack, 3, 4, 1, 1, 0, 0, false)
|
|
|
|
g.AddItem(inputField, 4, 0, 1, 5, 0, 0, false)
|
|
g.AddItem(statusBuf, 5, 0, 1, 5, 0, 0, false)
|
|
|
|
app.SetRoot(g, true)
|
|
|
|
app.SetBeforeFocusFunc(func(p cview.Primitive) bool {
|
|
return p == nil || p == inputField
|
|
})
|
|
|
|
focusUpdated()
|
|
|
|
statusMessage(defaultStatusText)
|
|
|
|
go connect(connectAddress)
|
|
|
|
go func() {
|
|
if err := app.Run(); err != nil {
|
|
log.Fatalf("failed to run application: %s", err)
|
|
}
|
|
|
|
exit <- true
|
|
}()
|
|
|
|
<-exit
|
|
}
|