package plays import ( "fmt" "net/url" "sync" "gitea.konchin.com/ytshih/inp2025/game/server/wordle" "gitea.konchin.com/ytshih/inp2025/game/tracing" "gitea.konchin.com/ytshih/inp2025/game/types" tea "github.com/charmbracelet/bubbletea" "github.com/gorilla/websocket" "github.com/vmihailenco/msgpack/v5" "go.uber.org/zap" ) type Game struct { *Base server *url.URL roomID string mutex sync.RWMutex state types.WordleState input string } func (m *Game) GetState() { urlStr := m.server.String() tracing.Logger.Info("dialing websocket", zap.String("url", urlStr)) c, _, err := websocket.DefaultDialer.Dial(urlStr, m.Base.client.Header) if err != nil { panic(fmt.Errorf("failed to dial, %w", err)) } defer c.Close() for { _, b, err := c.ReadMessage() switch err.(type) { case nil: var state types.WordleState if err := msgpack.Unmarshal(b, &state); err != nil { panic(fmt.Errorf("failed to unmarshal state, %w", err)) } m.mutex.Lock() m.state = state m.mutex.Unlock() case *websocket.CloseError: return default: panic(fmt.Errorf("failed to read message, %w", err)) } } } func NewGame(base *Base, server *url.URL, roomID string) *Game { m := &Game{ Base: base, server: server, roomID: roomID, state: types.NewWordleState(), input: "", } go m.GetState() return m } func (m *Game) Init() tea.Cmd { return tea.Batch(tea.ClearScreen) } func (m *Game) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if _, cmd := m.Base.Update(msg); cmd != nil { return m, cmd } switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyBackspace: if len(m.input) > 0 { m.input = m.input[:len(m.input)-1] } case tea.KeyEnter: if len(m.input) == types.MaxLength { return m, tea.Quit } case tea.KeyRunes: if len(m.input) < types.MaxLength { m.input = m.input + string(msg.Runes) } } } return m, nil } func (m *Game) View() string { m.mutex.RLock() ret := m.state.View() m.mutex.RUnlock() return ret } func (m *Game) Next(queue *[]*tea.Program) error { if len(m.input) == types.MaxLength { b, err := msgpack.Marshal(wordle.Operation{ Type: wordle.OperationTypeGuess, Username: m.username, Guess: m.input, }) if err != nil { return err } _, err = m.Base.client.R(). SetBody(b). Post(fmt.Sprintf("/api/rooms/%s/guess", m.roomID)) if err != nil { return err } *queue = append(*queue, tea.NewProgram(NewGame(m.Base, m.server, m.roomID))) } return nil }