177 lines
4.0 KiB
Go
177 lines
4.0 KiB
Go
package stages
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.konchin.com/ytshih/inp2025/handlers/wordle"
|
|
"gitea.konchin.com/ytshih/inp2025/types"
|
|
"github.com/charmbracelet/bubbles/textinput"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/vmihailenco/msgpack/v5"
|
|
)
|
|
|
|
const (
|
|
WEBSOCKET_RETRY int = 5
|
|
WEBSOCKET_BACKOFF time.Duration = 100 * time.Millisecond
|
|
)
|
|
|
|
type WordleClientModel struct {
|
|
*BaseModel
|
|
conn *websocket.Conn
|
|
state types.WordleState
|
|
|
|
input textinput.Model
|
|
err error
|
|
}
|
|
|
|
func NewWordleClientModel(
|
|
base *BaseModel,
|
|
) *WordleClientModel {
|
|
input := textinput.New()
|
|
input.Focus()
|
|
input.CharLimit = types.GUESS_WORD_LENGTH
|
|
input.Width = types.GUESS_WORD_LENGTH
|
|
return &WordleClientModel{
|
|
BaseModel: base,
|
|
input: input,
|
|
}
|
|
}
|
|
|
|
func (m *WordleClientModel) getState() tea.Cmd {
|
|
return func() tea.Msg {
|
|
if m.conn == nil {
|
|
u, err := url.Parse(m.client.BaseURL)
|
|
if err != nil {
|
|
m.err = fmt.Errorf("failed to parse BaseURL, %w", err)
|
|
}
|
|
u.Scheme = "ws"
|
|
u.Path = "/api/state"
|
|
|
|
for try := 0; try < WEBSOCKET_RETRY; try++ {
|
|
req, _ := http.NewRequest("GET",
|
|
/*placeholder*/ "http://localhost", nil)
|
|
req.SetBasicAuth(
|
|
m.client.UserInfo.Username,
|
|
m.client.UserInfo.Password)
|
|
m.conn, _, err = websocket.DefaultDialer.Dial(
|
|
u.String(), req.Header)
|
|
if err == nil {
|
|
break
|
|
}
|
|
time.Sleep(WEBSOCKET_BACKOFF)
|
|
}
|
|
if err != nil {
|
|
m.err = fmt.Errorf("failed to dial, %w", err)
|
|
}
|
|
}
|
|
|
|
_, b, err := m.conn.ReadMessage()
|
|
if err == nil {
|
|
var state types.WordleState
|
|
if err := msgpack.Unmarshal(b, &state); err != nil {
|
|
m.err = fmt.Errorf("failed to unmarshal state, %w", err)
|
|
}
|
|
return state
|
|
} else {
|
|
m.err = fmt.Errorf("failed to read message, %w", err)
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *WordleClientModel) postGuess(guess string) tea.Cmd {
|
|
return func() tea.Msg {
|
|
b, err := msgpack.Marshal(wordle.PostGuessInput{
|
|
Guess: guess,
|
|
})
|
|
if err != nil {
|
|
m.err = fmt.Errorf("failed to post guess, %w", err)
|
|
}
|
|
_, err = m.client.R().
|
|
SetBody(b).
|
|
Post("/api/guess")
|
|
if err != nil {
|
|
m.err = fmt.Errorf("failed to post guess, %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (m *WordleClientModel) Init() tea.Cmd {
|
|
return tea.Sequence(tea.ClearScreen,
|
|
tea.Batch(m.getState(), textinput.Blink))
|
|
}
|
|
|
|
func (m *WordleClientModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
var cmds []tea.Cmd
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c":
|
|
m.conn.Close()
|
|
return m, tea.Quit
|
|
case "enter":
|
|
if m.state.GameEnd {
|
|
m.conn.Close()
|
|
p := tea.NewProgram(NewLobbyModel(m.BaseModel))
|
|
m.Push(types.Program{
|
|
Run: func() error { _, err := p.Run(); return err },
|
|
Stage: types.StageLobby,
|
|
})
|
|
return m, tea.Quit
|
|
}
|
|
if len(m.input.Value()) == types.GUESS_WORD_LENGTH {
|
|
input := strings.ToUpper(m.input.Value())
|
|
m.input.Reset()
|
|
cmds = append(cmds, m.postGuess(input))
|
|
}
|
|
}
|
|
case types.WordleState:
|
|
m.state = msg
|
|
if m.state.GameEnd {
|
|
m.input.Blur()
|
|
}
|
|
if _, ok := m.state.CurrentGuess[m.client.UserInfo.Username]; ok {
|
|
m.input.Blur()
|
|
} else {
|
|
cmds = append(cmds, m.input.Focus())
|
|
}
|
|
cmds = append(cmds, m.getState())
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
m.input, cmd = m.input.Update(msg)
|
|
cmds = append(cmds, cmd)
|
|
return m, tea.Batch(cmds...)
|
|
}
|
|
|
|
func (m *WordleClientModel) View() string {
|
|
var b strings.Builder
|
|
|
|
fmt.Fprintf(&b, "Login as '%s'\n", m.client.UserInfo.Username)
|
|
fmt.Fprintf(&b, "remote addr: %s\n", m.client.BaseURL)
|
|
|
|
b.WriteString(m.state.View() + "\n")
|
|
b.WriteString(types.NewWordBank(m.state).View() + "\n")
|
|
|
|
if m.state.GameEnd {
|
|
fmt.Fprintf(&b, "Game End. Press 'Enter' to lobby.\n")
|
|
} else {
|
|
if guess, ok := m.state.CurrentGuess[m.client.UserInfo.Username]; ok {
|
|
fmt.Fprintf(&b, "Current guess for the round: %s\n", guess)
|
|
} else {
|
|
fmt.Fprintf(&b, "guess: %s\n", m.input.View())
|
|
}
|
|
if m.err != nil {
|
|
fmt.Fprintf(&b, "error: %+v\n", m.err)
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|