crib/main.go

329 lines
7.3 KiB
Go
Raw Permalink Normal View History

2020-06-06 11:26:07 +00:00
package main
import (
2020-08-12 23:12:50 +00:00
"flag"
"fmt"
2020-06-06 11:26:07 +00:00
"log"
2020-08-19 23:10:41 +00:00
"strings"
2020-08-20 00:05:17 +00:00
"sync"
"time"
2020-08-20 00:05:17 +00:00
"github.com/gdamore/tcell/v2"
2020-08-16 23:30:47 +00:00
"gitlab.com/tslocum/cbind"
2020-06-06 11:26:07 +00:00
"gitlab.com/tslocum/cview"
"gitlab.com/tslocum/joker"
2020-08-19 22:22:41 +00:00
cribbage "gitlab.com/tslocum/joker-cribbage"
2020-06-06 11:26:07 +00:00
)
2020-08-20 00:05:17 +00:00
const (
2020-10-04 04:26:32 +00:00
defaultStatusText = "Press Enter to chat, 1-6 to throw a card, Space to continue"
logFormat = "2006-01-02 15:04:05"
2020-08-20 00:05:17 +00:00
)
2020-06-06 11:26:07 +00:00
var (
2020-08-20 00:05:17 +00:00
debug bool
2020-08-16 23:30:47 +00:00
app *cview.Application
inputConfig *cbind.Configuration
2020-10-04 04:26:32 +00:00
statusText *statusTextView
2020-08-20 00:05:17 +00:00
gameText *cview.TextView
2020-08-16 23:30:47 +00:00
2020-08-20 00:05:17 +00:00
inputField *cview.InputField
statusBuf *cview.TextView
2020-08-16 23:30:47 +00:00
statusGrid *cview.Grid
throwGrid *cview.Grid
starterWidget *cardWidget
2020-08-15 20:25:20 +00:00
mainHandStack *cardStackWidget
mainCribStack *cardStackWidget
2020-08-16 23:30:47 +00:00
throwStack *cardStackWidget
2020-08-15 20:25:20 +00:00
oppCribStack *cardStackWidget
2020-08-16 23:30:47 +00:00
2020-10-04 04:26:32 +00:00
currentGameState = &gameState{}
2020-08-16 23:30:47 +00:00
writeBuffer = make(chan string)
exit = make(chan bool)
2020-08-20 00:05:17 +00:00
inputFocused bool
wroteFirstLogMessage bool
logMutex sync.Mutex
2020-06-06 11:26:07 +00:00
)
2020-08-16 23:30:47 +00:00
func setupThrowGrid() {
2020-08-19 22:22:41 +00:00
if throwGrid == nil {
throwGrid = cview.NewGrid()
}
throwGrid.Clear()
2020-08-16 23:30:47 +00:00
if currentGameState.Phase == PhasePeg {
2020-08-19 22:22:41 +00:00
throwGrid.SetColumns(-1)
throwGrid.AddItem(throwStack, 0, 0, 1, 1, 0, 0, false)
2020-08-16 23:30:47 +00:00
} else {
2020-08-19 22:22:41 +00:00
throwGrid.SetColumns((cardBufferX * 5) + cardWidth)
2020-08-16 23:30:47 +00:00
2020-08-19 22:22:41 +00:00
throwGrid.AddItem(throwStack, 0, 0, 1, 1, 0, 0, false)
throwGrid.AddItem(oppCribStack, 0, 1, 1, 1, 0, 0, false)
2020-08-16 23:30:47 +00:00
}
}
// Starts at 1
2020-08-19 22:22:41 +00:00
func selectCardIndex(cardNumber int) {
2020-08-16 23:30:47 +00:00
if cardNumber <= 0 || cardNumber > len(mainHandStack.Cards) {
return
}
2020-08-15 19:40:48 +00:00
2020-08-19 23:10:41 +00:00
var selected int
2020-08-19 22:22:41 +00:00
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()
2020-08-19 23:10:41 +00:00
}
2020-08-15 19:40:48 +00:00
2020-08-19 23:10:41 +00:00
func throwCard(card joker.Card) {
if currentGameState.Phase == PhasePick {
2020-08-16 23:30:47 +00:00
var selected int
2020-08-19 23:10:41 +00:00
for i := range mainHandStack.Cards {
if mainHandStack.Cards[i].selected {
2020-08-16 23:30:47 +00:00
selected++
}
}
2020-08-19 23:10:41 +00:00
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 {
2020-08-19 22:22:41 +00:00
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())
}
}
}
2020-08-16 23:30:47 +00:00
func doAction() {
var cards joker.Cards
for _, card := range mainHandStack.Cards {
if card.selected {
cards = append(cards, card.Card)
}
}
2020-08-19 23:10:41 +00:00
if len(cards) > 0 {
for i := range cards {
throwCard(cards[i])
2020-08-16 23:30:47 +00:00
}
return
}
writeBuffer <- "continue"
}
func setStatusText(status string) {
2020-08-20 00:08:14 +00:00
app.QueueUpdateDraw(func() {
statusText.SetText(" " + status)
})
2020-08-15 19:40:48 +00:00
}
2020-08-19 22:22:41 +00:00
func updateGameText() {
2020-08-19 23:10:41 +00:00
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)
2020-08-19 22:22:41 +00:00
}
2020-08-20 00:05:17 +00:00
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
}
2020-10-04 04:26:32 +00:00
statusBuf.Write([]byte(prefix + time.Now().Format(logFormat) + " " + message))
2020-08-20 00:05:17 +00:00
app.Draw()
}
2020-08-12 23:12:50 +00:00
func main() {
2020-08-19 23:10:41 +00:00
cview.Styles.PrimaryTextColor = tcell.ColorDefault
cview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault
2020-08-12 23:12:50 +00:00
var connectAddress string
2020-08-20 00:05:17 +00:00
flag.BoolVar(&debug, "debug", false, "print debug information")
2020-08-12 23:12:50 +00:00
flag.StringVar(&connectAddress, "server", "wss://play.cribbage.world/crib", "Server address")
flag.Parse()
2020-06-06 11:26:07 +00:00
2020-08-12 23:12:50 +00:00
app = cview.NewApplication().EnableMouse(true)
2020-06-06 11:26:07 +00:00
2020-08-16 23:30:47 +00:00
inputConfig = cbind.NewConfiguration()
inputConfig.SetKey(tcell.ModNone, tcell.KeyEnter, func(ev *tcell.EventKey) *tcell.EventKey {
2020-08-20 00:05:17 +00:00
toggleFocus()
2020-08-16 23:30:47 +00:00
return nil
})
inputConfig.SetRune(tcell.ModNone, ' ', func(ev *tcell.EventKey) *tcell.EventKey {
2020-08-20 00:05:17 +00:00
if inputFocused {
return ev
}
2020-08-16 23:30:47 +00:00
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 {
2020-08-20 00:05:17 +00:00
if inputFocused {
return ev
}
2020-08-19 22:22:41 +00:00
selectCardIndex(i)
2020-08-16 23:30:47 +00:00
app.Draw()
return nil
})
}
app.SetInputCapture(inputConfig.Capture)
2020-10-04 04:26:32 +00:00
starterWidget = newCardWidget(joker.Card{}, nil)
mainHandStack = newCardStackWidget()
mainCribStack = newCardStackWidget()
throwStack = newCardStackWidget()
oppCribStack = newCardStackWidget()
2020-08-15 19:40:48 +00:00
2020-08-16 23:30:47 +00:00
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).
2020-08-20 00:05:17 +00:00
SetRows(cardHeight, 1, 1, cardHeight+1, 2, -1)
2020-08-16 23:30:47 +00:00
2020-08-19 23:10:41 +00:00
gameText = cview.NewTextView()
2020-08-19 22:22:41 +00:00
gameText.SetTextAlign(cview.AlignCenter)
2020-08-15 19:40:48 +00:00
2020-08-16 23:30:47 +00:00
statusGrid = cview.NewGrid().
SetColumns(1, -1, 1).
2020-08-19 23:10:41 +00:00
SetRows(1, 1, -1)
2020-08-16 23:30:47 +00:00
statusGrid.AddItem(cview.NewTextView(), 0, 0, 3, 1, 0, 0, false)
2020-08-19 22:22:41 +00:00
statusGrid.AddItem(cview.NewTextView(), 0, 1, 1, 2, 0, 0, false)
2020-08-16 23:30:47 +00:00
statusGrid.AddItem(cview.NewTextView(), 1, 0, 1, 1, 0, 0, false)
2020-08-19 23:10:41 +00:00
statusGrid.AddItem(gameText, 2, 1, 1, 1, 0, 0, false)
2020-06-06 11:26:07 +00:00
2020-10-04 04:26:32 +00:00
statusText = newStatusTextView()
2020-06-06 11:26:07 +00:00
2020-08-20 00:05:17 +00:00
inputField = cview.NewInputField().
SetText("").
SetLabel("> ").
SetFieldWidth(0).
SetFieldBackgroundColor(tcell.ColorDefault).
SetFieldTextColor(tcell.ColorDefault)
2020-08-12 23:12:50 +00:00
statusBuf = cview.NewTextView().SetWrap(true).SetWordWrap(true)
2020-06-06 11:26:07 +00:00
2020-08-16 23:30:47 +00:00
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)
2020-08-19 23:10:41 +00:00
g.AddItem(cview.NewTextView(), 1, 0, 1, 5, 0, 0, false)
g.AddItem(statusText, 2, 0, 1, 5, 0, 0, false)
2020-08-16 23:30:47 +00:00
2020-08-19 23:10:41 +00:00
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)
2020-08-16 23:30:47 +00:00
2020-08-20 00:05:17 +00:00
g.AddItem(inputField, 4, 0, 1, 5, 0, 0, false)
g.AddItem(statusBuf, 5, 0, 1, 5, 0, 0, false)
2020-06-06 11:26:07 +00:00
app.SetRoot(g, true)
2020-08-20 00:05:17 +00:00
app.SetBeforeFocusFunc(func(p cview.Primitive) bool {
return p == nil || p == inputField
})
focusUpdated()
2020-10-04 04:26:32 +00:00
statusMessage(defaultStatusText)
2020-08-20 00:05:17 +00:00
2020-08-12 23:12:50 +00:00
go connect(connectAddress)
2020-06-06 11:26:07 +00:00
go func() {
if err := app.Run(); err != nil {
log.Fatalf("failed to run application: %s", err)
}
exit <- true
}()
<-exit
}