summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/ui/multiplayer/multiplayer.go8
-rw-r--r--pkg/ui/views/api.go44
-rw-r--r--pkg/ui/views/game.go116
-rw-r--r--pkg/ui/views/play.go107
-rw-r--r--pkg/ui/views/views.go4
5 files changed, 176 insertions, 103 deletions
diff --git a/pkg/ui/multiplayer/multiplayer.go b/pkg/ui/multiplayer/multiplayer.go
index 2f889c8..436388f 100644
--- a/pkg/ui/multiplayer/multiplayer.go
+++ b/pkg/ui/multiplayer/multiplayer.go
@@ -5,15 +5,15 @@ import (
)
type GameNetwork struct {
- server *network.TCPNetwork
- peer string
+ Server *network.TCPNetwork
+ Peer string
}
func NewGameNetwork(localID, localIP string, localPort int, callback func()) *GameNetwork {
server := network.NewTCPNetwork(localID, localIP, localPort, callback)
peer := ""
return &GameNetwork{
- server: server,
- peer: peer,
+ Server: server,
+ Peer: peer,
}
}
diff --git a/pkg/ui/views/api.go b/pkg/ui/views/api.go
new file mode 100644
index 0000000..3788f91
--- /dev/null
+++ b/pkg/ui/views/api.go
@@ -0,0 +1,44 @@
+package views
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "net/http"
+ "os"
+)
+
+// getAuthorizationToken reads the authentication token from the .rahannarc file
+func getAuthorizationToken() (string, error) {
+ f, err := os.Open(".rahannarc")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ var authorization string
+ for scanner.Scan() {
+ authorization = scanner.Text()
+ }
+
+ if err := scanner.Err(); err != nil {
+ return "", fmt.Errorf("error reading auth token: %v", err)
+ }
+
+ return authorization, nil
+}
+
+// sendAPIRequest sends an HTTP request to the API with the given parameters
+func sendAPIRequest(method, url string, payload []byte, authorization string) (*http.Response, error) {
+ req, err := http.NewRequest(method, url, bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
+
+ client := &http.Client{}
+ return client.Do(req)
+}
diff --git a/pkg/ui/views/game.go b/pkg/ui/views/game.go
index e26e3c9..52d8459 100644
--- a/pkg/ui/views/game.go
+++ b/pkg/ui/views/game.go
@@ -1,7 +1,11 @@
package views
import (
+ "encoding/json"
"fmt"
+ "os"
+ "strconv"
+ "strings"
"github.com/boozec/rahanna/internal/api/database"
"github.com/boozec/rahanna/pkg/ui/multiplayer"
@@ -11,7 +15,7 @@ import (
"github.com/charmbracelet/lipgloss"
)
-// Keyboard controls
+// gameKeyMap defines the key bindings for the game view.
type gameKeyMap struct {
EnterNewGame key.Binding
StartNewGame key.Binding
@@ -19,18 +23,19 @@ type gameKeyMap struct {
Quit key.Binding
}
-// Default key bindings for the game model
+// defaultGameKeyMap provides the default key bindings for the game view.
var defaultGameKeyMap = gameKeyMap{
GoLogout: key.NewBinding(
- key.WithKeys("alt+Q", "alt+q"),
+ key.WithKeys("alt+q"),
key.WithHelp("Alt+Q", "Logout"),
),
Quit: key.NewBinding(
- key.WithKeys("Q", "q"),
- key.WithHelp(" Q", "Quit"),
+ key.WithKeys("q"),
+ key.WithHelp("Q", "Quit"),
),
}
+// GameModel represents the state of the game view.
type GameModel struct {
// UI dimensions
width int
@@ -40,25 +45,30 @@ type GameModel struct {
keys playKeyMap
// Game state
- game *database.Game
- network *multiplayer.GameNetwork
+ peer string
+ currentGameID int
+ game *database.Game
+ network *multiplayer.GameNetwork
}
-func NewGameModel(width, height int, game *database.Game, network *multiplayer.GameNetwork) GameModel {
+// NewGameModel creates a new GameModel.
+func NewGameModel(width, height int, peer string, currentGameID int, network *multiplayer.GameNetwork) GameModel {
return GameModel{
- width: width,
- height: height,
- game: game,
- network: network,
+ width: width,
+ height: height,
+ peer: peer,
+ currentGameID: currentGameID,
+ network: network,
}
}
-// Init function for GameModel
+// Init initializes the GameModel.
func (m GameModel) Init() tea.Cmd {
ClearScreen()
- return textinput.Blink
+ return tea.Batch(textinput.Blink, m.getGame())
}
+// Update handles incoming messages and updates the GameModel.
func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if exit := handleExit(msg); exit != nil {
return m, exit
@@ -69,22 +79,29 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.handleWindowSize(msg)
case tea.KeyMsg:
return m.handleKeyPress(msg)
+ case database.Game:
+ return m.handleGetGameResponse(msg)
}
return m, nil
}
-// View function for GameModel
+// View renders the GameModel.
func (m GameModel) View() string {
formWidth := getFormWidth(m.width)
- // base := lipgloss.NewStyle().Align(lipgloss.Center).Width(m.width)
- content := "abc"
+ var content string
+ if m.game != nil {
+ otherPlayer := ""
+ if m.peer == "peer-1" {
+ otherPlayer = m.game.Player2.Username
+ } else {
+ otherPlayer = m.game.Player1.Username
+ }
+ content = fmt.Sprintf("You're playing versus %s", otherPlayer)
+ }
- // Build the main window with error handling
windowContent := m.buildWindowContent(content, formWidth)
-
- // Create navigation buttons
buttons := m.renderNavigationButtons()
centeredContent := lipgloss.JoinVertical(
@@ -113,11 +130,9 @@ func (m GameModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
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
}
-
return m, nil
}
@@ -146,3 +161,60 @@ func (m GameModel) renderNavigationButtons() string {
quitKey,
)
}
+
+func (m *GameModel) handleGetGameResponse(msg database.Game) (tea.Model, tea.Cmd) {
+ m.game = &msg
+ if m.peer == "peer-1" {
+ m.network.Peer = msg.IP2
+ } else {
+ m.network.Peer = msg.IP1
+ }
+ return m, nil
+}
+
+func (m *GameModel) getGame() tea.Cmd {
+ return func() tea.Msg {
+ var game database.Game
+
+ // Get authorization token
+ authorization, err := getAuthorizationToken()
+ if err != nil {
+ return nil
+ }
+
+ // Send API request
+ url := fmt.Sprintf("%s/play/%d", os.Getenv("API_BASE"), m.currentGameID)
+ resp, err := sendAPIRequest("GET", url, nil, authorization)
+ if err != nil {
+ return nil
+ }
+ defer resp.Body.Close()
+
+ if err := json.NewDecoder(resp.Body).Decode(&game); err != nil {
+ return nil
+ }
+
+ // Establish peer connection
+ if m.peer == "peer-1" {
+ if game.IP2 != "" {
+ ipParts := strings.Split(game.IP2, ":")
+ if len(ipParts) == 2 {
+ remoteIP := ipParts[0]
+ remotePortInt, _ := strconv.Atoi(ipParts[1])
+ go m.network.Server.AddPeer("peer-2", remoteIP, remotePortInt)
+ }
+ }
+ } else {
+ if game.IP1 != "" {
+ ipParts := strings.Split(game.IP1, ":")
+ if len(ipParts) == 2 {
+ remoteIP := ipParts[0]
+ remotePortInt, _ := strconv.Atoi(ipParts[1])
+ go m.network.Server.AddPeer("peer-1", remoteIP, remotePortInt)
+ }
+ }
+ }
+
+ return game
+ }
+}
diff --git a/pkg/ui/views/play.go b/pkg/ui/views/play.go
index 95621a3..4801556 100644
--- a/pkg/ui/views/play.go
+++ b/pkg/ui/views/play.go
@@ -1,12 +1,11 @@
package views
import (
- "bufio"
- "bytes"
"encoding/json"
"fmt"
"net/http"
"os"
+ "strconv"
"strings"
"github.com/boozec/rahanna/internal/api/database"
@@ -21,7 +20,7 @@ import (
const (
chessBoard = `
- A B C D E F G H
+A B C D E F G H
+---------------+
8 |♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜| 8
7 |♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟| 7
@@ -32,7 +31,7 @@ const (
2 |♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙| 2
1 |♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖| 1
+---------------+
- A B C D E F G H
+A B C D E F G H
`
)
@@ -47,9 +46,10 @@ const (
)
type responseOk struct {
- Name string `json:"name"`
- IP string `json:"ip"`
- Port int `json:"int"`
+ Name string `json:"name"`
+ GameID int `json:"id"`
+ IP string `json:"ip"`
+ Port int `json:"int"`
}
// API response types
@@ -110,10 +110,11 @@ type PlayModel struct {
paginator paginator.Model
// Game state
- playName string
- game *database.Game
- network *multiplayer.GameNetwork
- games []database.Game // Store the list of games
+ playName string
+ currentGameId int
+ game *database.Game
+ network *multiplayer.GameNetwork
+ games []database.Game
}
// NewPlayModel creates a new play model instance
@@ -159,7 +160,7 @@ func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
select {
case <-start:
- return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, m.game, m.network))
+ return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-1", m.currentGameId, m.network))
default:
}
@@ -178,13 +179,6 @@ func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.handleError(msg)
}
- // Handle input updates when on the InsertCodePage
- if m.page == InsertCodePage {
- var cmd tea.Cmd
- m.namePrompt, cmd = m.namePrompt.Update(msg)
- return m, cmd
- }
-
return m, nil
}
@@ -223,10 +217,13 @@ func (m PlayModel) handleWindowSize(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd)
}
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
- return m, nil
+ m.namePrompt, cmd = m.namePrompt.Update("suca")
+ return m, cmd
case key.Matches(msg, m.keys.StartNewGame):
m.page = StartGamePage
@@ -250,6 +247,11 @@ func (m PlayModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.paginator, _ = m.paginator.Update(msg)
+ if m.page == InsertCodePage {
+ m.namePrompt, cmd = m.namePrompt.Update(msg)
+ return m, cmd
+ }
+
return m, nil
}
@@ -264,6 +266,7 @@ func (m *PlayModel) handlePlayResponse(msg playResponse) (tea.Model, tea.Cmd) {
}
} else {
m.playName = msg.Ok.Name
+ m.currentGameId = msg.Ok.GameID
m.network = multiplayer.NewGameNetwork("peer-1", msg.Ok.IP, msg.Ok.Port, func() {
close(start)
@@ -277,6 +280,14 @@ 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)
+ network := multiplayer.NewGameNetwork("peer-2", localIP, int(localPort), func() {})
+
+ return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-2", m.game.ID, network))
+ }
return m, nil
}
@@ -307,7 +318,6 @@ func (m PlayModel) renderPageContent(base lipgloss.Style) string {
return base.Render(lipgloss.JoinVertical(lipgloss.Center, strings.Join(gamesStrings, "\n"), pageInfo))
}
case InsertCodePage:
- m.namePrompt.Focus()
return m.renderInsertCodeContent(base)
case StartGamePage:
@@ -358,23 +368,6 @@ func (m PlayModel) renderInsertCodeContent(base lipgloss.Style) string {
)
}
- // When we have a play, show who we're playing against
- if m.game != nil {
- playerName := lipgloss.NewStyle().
- Foreground(lipgloss.Color("#e67e22")).
- Render(m.game.Player1.Username)
-
- statusMsg := fmt.Sprintf("You are playing versus %s", playerName)
-
- return base.Render(
- lipgloss.NewStyle().
- Align(lipgloss.Center).
- Width(m.width).
- Bold(true).
- Render(statusMsg),
- )
- }
-
// Default: show input prompt
return base.Render(
lipgloss.JoinVertical(lipgloss.Left,
@@ -518,13 +511,14 @@ func (m *PlayModel) newGameCallback() tea.Cmd {
// 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, IP: ip, Port: port}}
+ return playResponse{Ok: responseOk{Name: response.Name, GameID: response.ID, IP: ip, Port: port}}
}
}
@@ -582,41 +576,6 @@ func (m PlayModel) enterGame() tea.Cmd {
}
}
-// getAuthorizationToken reads the authentication token from the .rahannarc file
-func getAuthorizationToken() (string, error) {
- f, err := os.Open(".rahannarc")
- if err != nil {
- return "", err
- }
- defer f.Close()
-
- scanner := bufio.NewScanner(f)
- var authorization string
- for scanner.Scan() {
- authorization = scanner.Text()
- }
-
- if err := scanner.Err(); err != nil {
- return "", fmt.Errorf("error reading auth token: %v", err)
- }
-
- return authorization, nil
-}
-
-// sendAPIRequest sends an HTTP request to the API with the given parameters
-func sendAPIRequest(method, url string, payload []byte, authorization string) (*http.Response, error) {
- req, err := http.NewRequest(method, url, bytes.NewReader(payload))
- if err != nil {
- return nil, err
- }
-
- req.Header.Set("Content-Type", "application/json")
- req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
-
- client := &http.Client{}
- return client.Do(req)
-}
-
func (m *PlayModel) fetchGames() tea.Cmd {
return func() tea.Msg {
var games []database.Game
diff --git a/pkg/ui/views/views.go b/pkg/ui/views/views.go
index 0e463b7..4ea08b6 100644
--- a/pkg/ui/views/views.go
+++ b/pkg/ui/views/views.go
@@ -100,9 +100,7 @@ func SwitchModelCmd(model tea.Model) tea.Cmd {
model: model,
}
- return func() tea.Msg {
- return s
- }
+ return tea.Batch(func() tea.Msg { return s }, s.model.Init())
}
func (m RahannaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {