summaryrefslogtreecommitdiff
path: root/pkg/ui/views/play.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/ui/views/play.go')
-rw-r--r--pkg/ui/views/play.go483
1 files changed, 0 insertions, 483 deletions
diff --git a/pkg/ui/views/play.go b/pkg/ui/views/play.go
index 3997a88..6ebebb3 100644
--- a/pkg/ui/views/play.go
+++ b/pkg/ui/views/play.go
@@ -1,18 +1,11 @@
package views
import (
- "encoding/json"
"fmt"
- "net/http"
- "os"
- "strconv"
"strings"
"github.com/boozec/rahanna/internal/api/database"
- "github.com/boozec/rahanna/internal/logger"
- "github.com/boozec/rahanna/internal/network"
"github.com/boozec/rahanna/pkg/ui/multiplayer"
- "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/paginator"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
@@ -36,67 +29,6 @@ A B C D E F G H
`
)
-type PlayModelPage int
-
-const (
- LandingPage PlayModelPage = iota
- InsertCodePage
- StartGamePage
-)
-
-type responseOk struct {
- Name string `json:"name"`
- GameID int `json:"id"`
- IP string `json:"ip"`
- Port int `json:"int"`
-}
-
-// API response types
-type playResponse struct {
- Ok responseOk
- Error string `json:"error"`
-}
-
-// Keyboard controls
-type playKeyMap struct {
- EnterNewGame key.Binding
- StartNewGame key.Binding
- GoLogout key.Binding
- Quit key.Binding
- NextPage key.Binding
- PrevPage key.Binding
-}
-
-// Default key bindings for the play model
-var defaultPlayKeyMap = playKeyMap{
- EnterNewGame: key.NewBinding(
- key.WithKeys("alt+E", "alt+e"),
- key.WithHelp("Alt+E", "Enter a play using code"),
- ),
- StartNewGame: key.NewBinding(
- key.WithKeys("alt+s", "alt+S"),
- key.WithHelp("Alt+S", "Start a new play"),
- ),
- GoLogout: key.NewBinding(
- key.WithKeys("alt+Q", "alt+q"),
- key.WithHelp("Alt+Q", "Logout"),
- ),
- Quit: key.NewBinding(
- key.WithKeys("Q", "q"),
- key.WithHelp(" Q", "Quit"),
- ),
- NextPage: key.NewBinding(
- key.WithKeys("right"),
- key.WithHelp("→/h", "Next Page"),
- ),
- PrevPage: key.NewBinding(
- key.WithKeys("left"),
- key.WithHelp("←/l", "Prev Page"),
- ),
-}
-
-type StartGameMsg struct{}
-
type PlayModel struct {
// UI dimensions
width int
@@ -136,19 +68,6 @@ func NewPlayModel(width, height int) PlayModel {
}
}
-// Create and configure the name input prompt
-func createNamePrompt(width int) textinput.Model {
- namePrompt := textinput.New()
- namePrompt.Prompt = " "
- namePrompt.TextStyle = inputStyle
- namePrompt.Placeholder = "rectangular-lake"
- namePrompt.Focus()
- namePrompt.CharLimit = 23
- namePrompt.Width = getFormWidth(width)
-
- return namePrompt
-}
-
func (m PlayModel) Init() tea.Cmd {
ClearScreen()
return tea.Batch(textinput.Blink, m.fetchGames())
@@ -206,405 +125,3 @@ func (m PlayModel) View() string {
centeredContent,
)
}
-
-func (m PlayModel) handleWindowSize(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) {
- m.width = msg.Width
- m.height = msg.Height
- return m, nil
-}
-
-func (m PlayModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
- var cmd tea.Cmd
-
- switch {
- case key.Matches(msg, m.keys.EnterNewGame):
- m.page = InsertCodePage
- m.namePrompt, cmd = m.namePrompt.Update("suca")
- return m, cmd
-
- case key.Matches(msg, m.keys.StartNewGame):
- m.page = StartGamePage
- if !m.isLoading {
- m.isLoading = true
- return m, m.newGameCallback()
- }
-
- case key.Matches(msg, m.keys.GoLogout):
- return m, logout(m.width, m.height+1)
-
- case key.Matches(msg, m.keys.Quit):
- return m, tea.Quit
-
- case msg.Type == tea.KeyEnter:
- if m.page == InsertCodePage && !m.isLoading {
- m.isLoading = true
- return m, m.enterGame()
- }
- }
-
- m.paginator, _ = m.paginator.Update(msg)
-
- if m.page == InsertCodePage {
- m.namePrompt, cmd = m.namePrompt.Update(msg)
- return m, cmd
- }
-
- return m, nil
-}
-
-func (m *PlayModel) handlePlayResponse(msg playResponse) (tea.Model, tea.Cmd) {
- m.isLoading = false
- m.err = nil
-
- if msg.Error != "" {
- m.err = fmt.Errorf("%s", msg.Error)
- if msg.Error == "unauthorized" {
- return m, logout(m.width, m.height+1)
- }
- } else {
- m.playName = msg.Ok.Name
- m.currentGameId = msg.Ok.GameID
- logger, _ := logger.GetLogger()
-
- callbackCompleted := make(chan bool)
- m.network = multiplayer.NewGameNetwork("peer-1", fmt.Sprintf("%s:%d", msg.Ok.IP, msg.Ok.Port), func() error {
- close(callbackCompleted)
- return nil
- }, logger)
-
- return m, func() tea.Msg {
- <-callbackCompleted
- return StartGameMsg{}
- }
- }
-
- return m, nil
-}
-
-func (m *PlayModel) handleGameResponse(msg database.Game) (tea.Model, tea.Cmd) {
- m.isLoading = false
- m.game = &msg
- m.err = nil
- ip := strings.Split(m.game.IP2, ":")
- if len(ip) == 2 {
- localIP := ip[0]
- localPort, _ := strconv.ParseInt(ip[1], 10, 32)
-
- logger, _ := logger.GetLogger()
- network := multiplayer.NewGameNetwork("peer-2", fmt.Sprintf("%s:%d", localIP, localPort), func() error {
- return nil
- }, logger)
-
- return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-1", m.game.ID, network))
- }
- return m, nil
-}
-
-func (m *PlayModel) handleGamesResponse(msg []database.Game) (tea.Model, tea.Cmd) {
- m.isLoading = false
- m.games = msg
- m.err = nil
- m.paginator.SetTotalPages(len(m.games))
- return m, nil
-}
-
-func (m *PlayModel) handleError(msg error) (tea.Model, tea.Cmd) {
- m.isLoading = false
- m.err = msg
- return m, nil
-}
-
-func (m PlayModel) renderPageContent(base lipgloss.Style) string {
- switch m.page {
- case LandingPage:
- m.namePrompt.Blur()
- if len(m.games) == 0 {
- return base.Render(chessBoard)
- } else {
- start, end := m.paginator.GetSliceBounds(len(m.games))
- gamesStrings := formatGamesForPage(m.games[start:end], altCodeStyle)
- pageInfo := m.paginator.View()
- return base.Render(lipgloss.JoinVertical(lipgloss.Center, strings.Join(gamesStrings, "\n"), pageInfo))
- }
- case InsertCodePage:
- return m.renderInsertCodeContent(base)
-
- case StartGamePage:
- return m.renderStartGameContent(base)
- }
-
- return ""
-}
-
-func formatGamesForPage(games []database.Game, altCodeStyle lipgloss.Style) []string {
- var gamesStrings []string
- gamesStrings = append(gamesStrings, "Games list")
-
- longestName := 0
- for _, game := range games {
- if len(game.Name) > longestName {
- longestName = len(game.Name)
- }
- }
-
- for i, game := range games {
- indexStr := altCodeStyle.Render(fmt.Sprintf("[%d] ", i))
- nameStr := game.Name
- dateStr := game.UpdatedAt.Format("2006-01-02 15:04")
-
- padding := longestName - len(nameStr)
- paddingStr := strings.Repeat(" ", padding+4)
-
- line := lipgloss.JoinHorizontal(lipgloss.Left,
- indexStr,
- nameStr,
- paddingStr,
- lipgloss.NewStyle().Foreground(lipgloss.Color("#d35400")).Render(dateStr),
- )
- gamesStrings = append(gamesStrings, line)
- }
- return gamesStrings
-}
-
-func (m PlayModel) renderInsertCodeContent(base lipgloss.Style) string {
- // When loading, show loading status
- if m.isLoading {
- return base.Render(
- lipgloss.NewStyle().
- Align(lipgloss.Center).
- Bold(true).
- Render("Loading..."),
- )
- }
-
- // Default: show input prompt
- return base.Render(
- lipgloss.JoinVertical(lipgloss.Left,
- lipgloss.NewStyle().Render("Insert play code:"),
- m.namePrompt.View(),
- lipgloss.NewStyle().
- Align(lipgloss.Center).
- PaddingTop(2).
- Bold(true).
- Render(fmt.Sprintf("Press %s to join",
- lipgloss.NewStyle().Italic(true).Render("Enter"))),
- ),
- )
-}
-
-func (m PlayModel) renderStartGameContent(base lipgloss.Style) string {
- var statusMsg string
-
- if m.isLoading {
- statusMsg = "Loading..."
- } else if m.playName != "" {
- gameCode := lipgloss.NewStyle().
- Italic(true).
- Foreground(lipgloss.Color("#F39C12")).
- Render(m.playName)
-
- statusMsg = fmt.Sprintf("Share `%s` to your friend", gameCode)
- }
-
- return base.Render(statusMsg)
-}
-
-func (m PlayModel) buildWindowContent(content string, formWidth int) string {
- if m.err != nil {
- formError := fmt.Sprintf("Error: %v", m.err.Error())
- return lipgloss.JoinVertical(
- lipgloss.Center,
- windowStyle.Width(formWidth).Render(lipgloss.JoinVertical(
- lipgloss.Center,
- errorStyle.Align(lipgloss.Center).Width(formWidth-4).Render(formError),
- content,
- )),
- )
- }
-
- return lipgloss.JoinVertical(
- lipgloss.Center,
- windowStyle.Width(formWidth).Render(lipgloss.JoinVertical(
- lipgloss.Center,
- content,
- )),
- )
-}
-
-func (m PlayModel) renderNavigationButtons() string {
- logoutKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.GoLogout.Help().Key),
- m.keys.GoLogout.Help().Desc)
-
- quitKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.Quit.Help().Key),
- m.keys.Quit.Help().Desc)
-
- if m.page == LandingPage {
- enterKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.EnterNewGame.Help().Key),
- m.keys.EnterNewGame.Help().Desc)
-
- startKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.StartNewGame.Help().Key),
- m.keys.StartNewGame.Help().Desc)
-
- nextPageKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.NextPage.Help().Key),
- m.keys.NextPage.Help().Desc)
-
- prevPageKey := fmt.Sprintf("%s %s",
- altCodeStyle.Render(m.keys.PrevPage.Help().Key),
- m.keys.PrevPage.Help().Desc)
-
- return lipgloss.JoinVertical(
- lipgloss.Left,
- enterKey,
- startKey,
- lipgloss.JoinHorizontal(lipgloss.Left, prevPageKey, " | ", nextPageKey),
- logoutKey,
- quitKey,
- )
- }
-
- return lipgloss.JoinVertical(
- lipgloss.Left,
- logoutKey,
- quitKey,
- )
-}
-
-func (m *PlayModel) newGameCallback() tea.Cmd {
- return func() tea.Msg {
- // Get authorization token
- authorization, err := getAuthorizationToken()
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- // Set up network connection
- port, err := network.GetRandomAvailablePort()
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- ip := network.GetOutboundIP().String()
- // FIXME: ip
- ip = "0.0.0.0"
-
- // Prepare request payload
- payload, err := json.Marshal(map[string]string{
- "ip": fmt.Sprintf("%s:%d", ip, port),
- })
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- // Send API request
- url := os.Getenv("API_BASE") + "/play"
- resp, err := sendAPIRequest("POST", url, payload, authorization)
- if err != nil {
- return playResponse{Error: err.Error()}
- }
- defer resp.Body.Close()
-
- // Handle response
- if resp.StatusCode != http.StatusOK {
- var response playResponse
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
- return playResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)}
- }
- return playResponse{Error: response.Error}
- }
-
- // Decode successful response
- var response struct {
- Name string `json:"name"`
- ID int `json:"id"`
- Error string `json:"error"`
- }
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
- return playResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)}
- }
-
- return playResponse{Ok: responseOk{Name: response.Name, GameID: response.ID, IP: ip, Port: port}}
- }
-}
-
-func (m PlayModel) enterGame() tea.Cmd {
- return func() tea.Msg {
- // Get authorization token
- authorization, err := getAuthorizationToken()
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- // Set up network connection
- port, err := network.GetRandomAvailablePort()
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- ip := network.GetOutboundIP().String()
- // FIXME: ip
- ip = "0.0.0.0"
-
- // Prepare request payload
- payload, err := json.Marshal(map[string]string{
- "ip": fmt.Sprintf("%s:%d", ip, port),
- "name": m.namePrompt.Value(),
- })
- if err != nil {
- return playResponse{Error: err.Error()}
- }
-
- // Send API request
- url := os.Getenv("API_BASE") + "/enter-game"
- resp, err := sendAPIRequest("POST", url, payload, authorization)
- if err != nil {
- return playResponse{Error: err.Error()}
- }
- defer resp.Body.Close()
-
- // Handle response
- if resp.StatusCode != http.StatusOK {
- var response playResponse
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
- return playResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)}
- }
- return playResponse{Error: response.Error}
- }
-
- // Decode successful response
- var response database.Game
- if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
- return playResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)}
- }
-
- return response
- }
-}
-
-func (m *PlayModel) fetchGames() tea.Cmd {
- return func() tea.Msg {
- var games []database.Game
- // Get authorization token
- authorization, err := getAuthorizationToken()
- if err != nil {
- return games
- }
-
- // Send API request
- url := os.Getenv("API_BASE") + "/play"
- resp, err := sendAPIRequest("GET", url, nil, authorization)
- if err != nil {
- return games
- }
- defer resp.Body.Close()
-
- if err := json.NewDecoder(resp.Body).Decode(&games); err != nil {
- return []database.Game{}
- }
- return games
- }
-}