Feat: works on my machine
This commit is contained in:
23
stages/base.go
Normal file
23
stages/base.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package stages
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type BaseModel struct {
|
||||
queue *[]*tea.Program
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewBaseModel(
|
||||
queue *[]*tea.Program,
|
||||
endpoint string,
|
||||
) *BaseModel {
|
||||
return &BaseModel{
|
||||
queue: queue,
|
||||
client: resty.New().
|
||||
SetBaseURL(endpoint).
|
||||
SetDisableWarn(true),
|
||||
}
|
||||
}
|
||||
241
stages/landing.go
Normal file
241
stages/landing.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package stages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.konchin.com/ytshih/inp2025/models"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type landingOperationType int
|
||||
|
||||
const (
|
||||
landingOperationChoose landingOperationType = iota
|
||||
landingOperationLoginCred
|
||||
landingOperationRegistCred
|
||||
)
|
||||
|
||||
type focusTargetType int
|
||||
|
||||
const (
|
||||
focusTargetUsername focusTargetType = iota
|
||||
focusTargetPassword
|
||||
)
|
||||
|
||||
type LandingModel struct {
|
||||
*BaseModel
|
||||
op landingOperationType
|
||||
|
||||
focus focusTargetType
|
||||
username textinput.Model
|
||||
password textinput.Model
|
||||
|
||||
info string
|
||||
err error
|
||||
}
|
||||
|
||||
func NewLandingModel(base *BaseModel) *LandingModel {
|
||||
username := textinput.New()
|
||||
username.Placeholder = "Username"
|
||||
username.CharLimit = 32
|
||||
username.Width = 32
|
||||
username.Blur()
|
||||
|
||||
password := textinput.New()
|
||||
password.Placeholder = "Password"
|
||||
password.CharLimit = 32
|
||||
password.Width = 32
|
||||
password.Blur()
|
||||
password.EchoMode = textinput.EchoPassword
|
||||
password.EchoCharacter = '•'
|
||||
|
||||
return &LandingModel{
|
||||
BaseModel: base,
|
||||
op: landingOperationChoose,
|
||||
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LandingModel) reset() {
|
||||
m.op = landingOperationChoose
|
||||
m.username.Blur()
|
||||
m.username.Reset()
|
||||
m.password.Blur()
|
||||
m.password.Reset()
|
||||
}
|
||||
|
||||
type postLoginMsg struct{}
|
||||
|
||||
func (m *LandingModel) postLogin() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resp, err := m.BaseModel.client.R().
|
||||
SetBasicAuth(m.username.Value(), m.password.Value()).
|
||||
Post("/auth/login")
|
||||
if err == nil {
|
||||
switch resp.StatusCode() {
|
||||
case http.StatusOK:
|
||||
m.BaseModel.client.SetBasicAuth(
|
||||
m.username.Value(), m.password.Value())
|
||||
m.info = "login success.\n"
|
||||
m.err = nil
|
||||
case http.StatusUnauthorized:
|
||||
m.err = fmt.Errorf("user not exist or password incorrect, %s",
|
||||
string(resp.Body()))
|
||||
default:
|
||||
m.err = fmt.Errorf("unknown server error, %s",
|
||||
string(resp.Body()))
|
||||
}
|
||||
} else {
|
||||
m.err = fmt.Errorf("failed to post login, %w", err)
|
||||
}
|
||||
return postLoginMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
type postRegisterMsg struct{}
|
||||
|
||||
func (m *LandingModel) postRegister() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
resp, err := m.BaseModel.client.R().
|
||||
SetBody(models.User{
|
||||
Username: m.username.Value(),
|
||||
Password: m.password.Value(),
|
||||
}).
|
||||
Post("/auth/register")
|
||||
if err == nil {
|
||||
switch resp.StatusCode() {
|
||||
case http.StatusOK:
|
||||
m.info = "register success.\n"
|
||||
m.err = nil
|
||||
case http.StatusBadRequest:
|
||||
m.err = fmt.Errorf("username already exist, %s",
|
||||
string(resp.Body()))
|
||||
default:
|
||||
m.err = fmt.Errorf("unknown server error, %s",
|
||||
string(resp.Body()))
|
||||
}
|
||||
} else {
|
||||
m.err = fmt.Errorf("failed to post register, %s",
|
||||
string(resp.Body()))
|
||||
}
|
||||
return postRegisterMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LandingModel) Init() tea.Cmd {
|
||||
return tea.ClearScreen
|
||||
}
|
||||
|
||||
func (m *LandingModel) 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":
|
||||
return m, tea.Quit
|
||||
case "L", "l":
|
||||
if m.op == landingOperationChoose {
|
||||
m.op = landingOperationLoginCred
|
||||
m.focus = focusTargetUsername
|
||||
cmds = append(cmds, m.username.Focus())
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
case "R", "r":
|
||||
if m.op == landingOperationChoose {
|
||||
m.op = landingOperationRegistCred
|
||||
m.focus = focusTargetUsername
|
||||
cmds = append(cmds, m.username.Focus())
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
case "shift+tab", "up":
|
||||
if m.op == landingOperationLoginCred ||
|
||||
m.op == landingOperationRegistCred {
|
||||
switch m.focus {
|
||||
case focusTargetUsername:
|
||||
m.username.Blur()
|
||||
m.focus = focusTargetPassword
|
||||
cmds = append(cmds, m.password.Focus())
|
||||
case focusTargetPassword:
|
||||
m.password.Blur()
|
||||
m.focus = focusTargetUsername
|
||||
cmds = append(cmds, m.username.Focus())
|
||||
}
|
||||
}
|
||||
case "tab", "down", "enter":
|
||||
if m.op == landingOperationLoginCred ||
|
||||
m.op == landingOperationRegistCred {
|
||||
switch m.focus {
|
||||
case focusTargetUsername:
|
||||
m.username.Blur()
|
||||
m.focus = focusTargetPassword
|
||||
cmds = append(cmds, m.password.Focus())
|
||||
case focusTargetPassword:
|
||||
if msg.String() == "enter" {
|
||||
switch m.op {
|
||||
case landingOperationLoginCred:
|
||||
cmds = append(cmds, m.postLogin())
|
||||
case landingOperationRegistCred:
|
||||
cmds = append(cmds, m.postRegister())
|
||||
}
|
||||
} else {
|
||||
m.password.Blur()
|
||||
m.focus = focusTargetUsername
|
||||
cmds = append(cmds, m.username.Focus())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case postLoginMsg:
|
||||
if m.err == nil {
|
||||
*m.queue = append(*m.queue,
|
||||
tea.NewProgram(NewLobbyModel(m.BaseModel)))
|
||||
return m, tea.Quit
|
||||
} else {
|
||||
m.reset()
|
||||
}
|
||||
case postRegisterMsg:
|
||||
m.reset()
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.username, cmd = m.username.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
m.password, cmd = m.password.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *LandingModel) View() string {
|
||||
var b strings.Builder
|
||||
|
||||
switch m.op {
|
||||
case landingOperationChoose:
|
||||
fmt.Fprintf(&b, "Choose Operation\n(L)ogin / (R)egister\n")
|
||||
case landingOperationLoginCred:
|
||||
fmt.Fprintf(&b, "Login: \n")
|
||||
b.WriteString(m.username.View() + "\n")
|
||||
b.WriteString(m.password.View() + "\n")
|
||||
case landingOperationRegistCred:
|
||||
fmt.Fprintf(&b, "Register: \n")
|
||||
b.WriteString(m.username.View() + "\n")
|
||||
b.WriteString(m.password.View() + "\n")
|
||||
}
|
||||
|
||||
if m.info != "" {
|
||||
b.WriteString(m.info + "\n")
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
b.WriteString("----------\n")
|
||||
b.WriteString(m.err.Error() + "\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
283
stages/lobby.go
Normal file
283
stages/lobby.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package stages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.konchin.com/ytshih/inp2025/types"
|
||||
"gitea.konchin.com/ytshih/inp2025/utils"
|
||||
"gitea.konchin.com/ytshih/inp2025/workflows"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
REFRESH_TIME = time.Second
|
||||
)
|
||||
|
||||
type lobbyOperationType int
|
||||
|
||||
const (
|
||||
lobbyOperationChoose lobbyOperationType = iota
|
||||
lobbyOperationServerWaiting
|
||||
lobbyOperationServerChoose
|
||||
lobbyOperationClientScannning
|
||||
)
|
||||
|
||||
type LobbyModel struct {
|
||||
*BaseModel
|
||||
op lobbyOperationType
|
||||
|
||||
shutdown types.ShutdownFunc
|
||||
local string
|
||||
remote string
|
||||
|
||||
// server
|
||||
remoteUser string
|
||||
listener net.Listener
|
||||
|
||||
// client
|
||||
cursor int
|
||||
endpoints []string
|
||||
|
||||
info string
|
||||
err error
|
||||
}
|
||||
|
||||
func NewLobbyModel(base *BaseModel) *LobbyModel {
|
||||
return &LobbyModel{
|
||||
BaseModel: base,
|
||||
op: lobbyOperationChoose,
|
||||
|
||||
shutdown: func() {},
|
||||
}
|
||||
}
|
||||
|
||||
type serverListenMsg struct{}
|
||||
|
||||
func (m *LobbyModel) serverListen() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
dataCh := make(chan string)
|
||||
m.local, m.shutdown, m.err = utils.ListenUDPData(
|
||||
viper.GetInt("udp-listen-port"), dataCh)
|
||||
if m.err != nil {
|
||||
m.err = fmt.Errorf("failed to listen, %w", m.err)
|
||||
return serverListenMsg{}
|
||||
}
|
||||
|
||||
req := <-dataCh
|
||||
m.shutdown()
|
||||
m.shutdown = func() {}
|
||||
|
||||
var joinRequest types.JoinRequest
|
||||
if err := msgpack.Unmarshal([]byte(req), &joinRequest); err != nil {
|
||||
m.err = fmt.Errorf("failed to unmarshal msgpack, %w", err)
|
||||
return serverListenMsg{}
|
||||
}
|
||||
|
||||
m.remote = joinRequest.Endpoint
|
||||
m.remoteUser = joinRequest.Username
|
||||
return serverListenMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
type serverSendReplyMsg struct{}
|
||||
|
||||
func (m *LobbyModel) serverSendReply(response bool) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if response {
|
||||
// Start Wordle Server listener
|
||||
m.listener, m.err = net.Listen("tcp4", ":0")
|
||||
if m.err != nil {
|
||||
m.err = fmt.Errorf("failed to listen on anonymous port, %w", m.err)
|
||||
return serverSendReplyMsg{}
|
||||
}
|
||||
|
||||
local := fmt.Sprintf("%s:%d",
|
||||
m.listener.Addr().(*net.TCPAddr).IP.String(),
|
||||
m.listener.Addr().(*net.TCPAddr).Port)
|
||||
m.err = utils.SendPayload(local, m.remote,
|
||||
types.JoinResponse{Endpoint: local})
|
||||
|
||||
// Store wordle server endpoint
|
||||
m.remote = local
|
||||
} else {
|
||||
m.err = utils.SendPayload("", m.remote,
|
||||
types.JoinResponse{Endpoint: ""})
|
||||
}
|
||||
return serverSendReplyMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
type clientScanMsg time.Time
|
||||
|
||||
func (m *LobbyModel) clientScan() tea.Cmd {
|
||||
return tea.Tick(REFRESH_TIME, func(t time.Time) tea.Msg {
|
||||
m.endpoints = []string{}
|
||||
for _, endpoint := range viper.GetStringSlice("udp-endpoints") {
|
||||
if err := utils.Ping(endpoint); err == nil {
|
||||
m.endpoints = append(m.endpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return clientScanMsg(t)
|
||||
})
|
||||
}
|
||||
|
||||
type clientJoinMsg struct{}
|
||||
|
||||
func (m *LobbyModel) clientJoin() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
dataCh := make(chan string)
|
||||
m.local, m.shutdown, m.err = utils.ListenUDPData(0, dataCh)
|
||||
if m.err != nil {
|
||||
m.err = fmt.Errorf("failed to listen udp data, %w", m.err)
|
||||
return clientJoinMsg{}
|
||||
}
|
||||
|
||||
m.err = utils.SendPayload(m.local, m.remote, types.JoinRequest{
|
||||
Endpoint: m.local,
|
||||
Username: m.client.UserInfo.Username,
|
||||
})
|
||||
if m.err != nil {
|
||||
m.err = fmt.Errorf("failed to send invitation, %w", m.err)
|
||||
return clientJoinMsg{}
|
||||
}
|
||||
|
||||
data := <-dataCh
|
||||
m.shutdown()
|
||||
m.shutdown = func() {}
|
||||
|
||||
var joinResponse types.JoinResponse
|
||||
if err := msgpack.Unmarshal([]byte(data), &joinResponse); err != nil {
|
||||
m.err = fmt.Errorf("failed to unmarshal msgpack, %w", err)
|
||||
return clientJoinMsg{}
|
||||
}
|
||||
|
||||
m.remote = joinResponse.Endpoint
|
||||
return clientJoinMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LobbyModel) Init() tea.Cmd {
|
||||
return tea.ClearScreen
|
||||
}
|
||||
|
||||
func (m *LobbyModel) 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":
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
switch m.op {
|
||||
case lobbyOperationChoose:
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "S", "s":
|
||||
m.op = lobbyOperationServerWaiting
|
||||
cmds = append(cmds, m.serverListen())
|
||||
case "C", "c":
|
||||
m.op = lobbyOperationClientScannning
|
||||
cmds = append(cmds, m.clientScan())
|
||||
}
|
||||
}
|
||||
case lobbyOperationServerWaiting:
|
||||
switch msg.(type) {
|
||||
case serverListenMsg:
|
||||
m.op = lobbyOperationServerChoose
|
||||
}
|
||||
case lobbyOperationServerChoose:
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "Y", "y", "enter":
|
||||
cmds = append(cmds, m.serverSendReply(true))
|
||||
case "N", "n":
|
||||
m.op = lobbyOperationServerWaiting
|
||||
cmds = append(cmds, m.serverSendReply(false))
|
||||
}
|
||||
case serverSendReplyMsg:
|
||||
if m.err == nil {
|
||||
m.shutdown()
|
||||
m.client.SetBaseURL("http://" + m.remote)
|
||||
shutdown := workflows.WordleServer(m.listener)
|
||||
*m.queue = append(*m.queue,
|
||||
tea.NewProgram(NewWordleClientModel(m.BaseModel, shutdown)))
|
||||
return m, tea.Quit
|
||||
} else {
|
||||
m.op = lobbyOperationServerWaiting
|
||||
}
|
||||
}
|
||||
case lobbyOperationClientScannning:
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "shift+tab", "up":
|
||||
if n := len(m.endpoints); n > 0 {
|
||||
m.cursor = (m.cursor - 1 + n) % n
|
||||
}
|
||||
case "tab", "down":
|
||||
if n := len(m.endpoints); n > 0 {
|
||||
m.cursor = (m.cursor + 1) % n
|
||||
}
|
||||
case "enter":
|
||||
m.remote = m.endpoints[m.cursor]
|
||||
cmds = append(cmds, m.clientJoin())
|
||||
}
|
||||
case clientScanMsg:
|
||||
cmds = append(cmds, m.clientScan())
|
||||
case clientJoinMsg:
|
||||
if m.err == nil && m.remote != "" {
|
||||
m.shutdown()
|
||||
m.client.SetBaseURL("http://" + m.remote)
|
||||
*m.BaseModel.queue = append(*m.BaseModel.queue,
|
||||
tea.NewProgram(NewWordleClientModel(m.BaseModel, func() {})))
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *LobbyModel) View() string {
|
||||
var b strings.Builder
|
||||
|
||||
switch m.op {
|
||||
case lobbyOperationChoose:
|
||||
b.WriteString("Choose Role\n(S)erver / (C)lient\n")
|
||||
case lobbyOperationServerWaiting:
|
||||
b.WriteString("Wait for user to join...\n")
|
||||
case lobbyOperationServerChoose:
|
||||
fmt.Fprintf(&b, "Receive Join Request by '%s'\nAccept (Y)/N\n",
|
||||
m.remoteUser)
|
||||
case lobbyOperationClientScannning:
|
||||
b.WriteString("Scanning server...\nChoose one to join\n")
|
||||
for i, endpoint := range m.endpoints {
|
||||
if i == m.cursor {
|
||||
fmt.Fprintf(&b, "(•) %s\n", endpoint)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "( ) %s\n", endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.info != "" {
|
||||
b.WriteString(m.info + "\n")
|
||||
}
|
||||
|
||||
if m.err != nil {
|
||||
b.WriteString("----------\n")
|
||||
b.WriteString(m.err.Error() + "\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
1
stages/udp.go
Normal file
1
stages/udp.go
Normal file
@@ -0,0 +1 @@
|
||||
package stages
|
||||
179
stages/wordle.go
Normal file
179
stages/wordle.go
Normal file
@@ -0,0 +1,179 @@
|
||||
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
|
||||
shutdown types.ShutdownFunc
|
||||
|
||||
input textinput.Model
|
||||
err error
|
||||
}
|
||||
|
||||
func NewWordleClientModel(
|
||||
base *BaseModel,
|
||||
shutdown types.ShutdownFunc,
|
||||
) *WordleClientModel {
|
||||
input := textinput.New()
|
||||
input.Focus()
|
||||
input.CharLimit = types.GUESS_WORD_LENGTH
|
||||
input.Width = types.GUESS_WORD_LENGTH
|
||||
return &WordleClientModel{
|
||||
BaseModel: base,
|
||||
input: input,
|
||||
shutdown: shutdown,
|
||||
}
|
||||
}
|
||||
|
||||
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.OperationGuess{
|
||||
Username: m.client.UserInfo.Username,
|
||||
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()
|
||||
m.shutdown()
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
if m.state.GameEnd {
|
||||
m.conn.Close()
|
||||
m.shutdown()
|
||||
*m.queue = append(*m.queue,
|
||||
tea.NewProgram(NewLobbyModel(m.BaseModel)))
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user