summaryrefslogtreecommitdiff
path: root/pkg/ui
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2025-04-16 21:37:09 +0200
committerSanto Cariotti <santo@dcariotti.me>2025-04-16 21:37:09 +0200
commitf60fadc54421c8e0aedb33e59270d4aa48e842d2 (patch)
treeb15aac242a7f4548ebbe55dd5c4165da2cd3395e /pkg/ui
parentba4afeb4ee19c24b393ec21d374bdd752651c1a6 (diff)
Send messages for randomm chess move
Diffstat (limited to 'pkg/ui')
-rw-r--r--pkg/ui/views/game.go88
-rw-r--r--pkg/ui/views/play.go23
2 files changed, 84 insertions, 27 deletions
diff --git a/pkg/ui/views/game.go b/pkg/ui/views/game.go
index 5a0972c..4c5ed71 100644
--- a/pkg/ui/views/game.go
+++ b/pkg/ui/views/game.go
@@ -3,36 +3,45 @@ package views
import (
"encoding/json"
"fmt"
+ "math/rand"
"os"
"github.com/boozec/rahanna/internal/api/database"
+ "github.com/boozec/rahanna/internal/network"
"github.com/boozec/rahanna/pkg/ui/multiplayer"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
+ "github.com/notnil/chess"
)
// gameKeyMap defines the key bindings for the game view.
type gameKeyMap struct {
- EnterNewGame key.Binding
- StartNewGame key.Binding
- GoLogout key.Binding
- Quit key.Binding
+ GoLogout key.Binding
+ RandomMove key.Binding
+ Quit key.Binding
}
// defaultGameKeyMap provides the default key bindings for the game view.
var defaultGameKeyMap = gameKeyMap{
+ RandomMove: key.NewBinding(
+ key.WithKeys("R", "r"),
+ key.WithHelp("R", "Random Move"),
+ ),
GoLogout: key.NewBinding(
- key.WithKeys("alt+q"),
+ key.WithKeys("alt+Q", "alt+q"),
key.WithHelp("Alt+Q", "Logout"),
),
Quit: key.NewBinding(
- key.WithKeys("q"),
- key.WithHelp("Q", "Quit"),
+ key.WithKeys("Q", "q"),
+ key.WithHelp(" Q", "Quit"),
),
}
+// ChessMoveMsg is a message containing a received chess move.
+type ChessMoveMsg string
+
// GameModel represents the state of the game view.
type GameModel struct {
// UI dimensions
@@ -40,13 +49,16 @@ type GameModel struct {
height int
// UI state
- keys playKeyMap
+ keys gameKeyMap
// Game state
peer string
currentGameID int
game *database.Game
network *multiplayer.GameNetwork
+ chessGame *chess.Game
+ incomingMoves chan string
+ turn int
}
// NewGameModel creates a new GameModel.
@@ -54,16 +66,20 @@ func NewGameModel(width, height int, peer string, currentGameID int, network *mu
return GameModel{
width: width,
height: height,
+ keys: defaultGameKeyMap,
peer: peer,
currentGameID: currentGameID,
network: network,
+ chessGame: chess.NewGame(chess.UseNotation(chess.UCINotation{})),
+ incomingMoves: make(chan string),
+ turn: 0,
}
}
// Init initializes the GameModel.
func (m GameModel) Init() tea.Cmd {
ClearScreen()
- return tea.Batch(textinput.Blink, m.getGame())
+ return tea.Batch(textinput.Blink, m.getGame(), m.getMoves())
}
// Update handles incoming messages and updates the GameModel.
@@ -77,6 +93,13 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.handleWindowSize(msg)
case tea.KeyMsg:
return m.handleKeyPress(msg)
+ case ChessMoveMsg:
+ m.turn++
+ err := m.chessGame.MoveStr(string(msg))
+ if err != nil {
+ fmt.Println("Error applying move:", err)
+ }
+ return m, m.getMoves()
case database.Game:
return m.handleGetGameResponse(msg)
}
@@ -90,13 +113,18 @@ func (m GameModel) View() string {
var content string
if m.game != nil {
- otherPlayer := ""
- if m.peer == "peer-1" {
- otherPlayer = m.game.Player2.Username
- } else {
- otherPlayer = m.game.Player1.Username
+ yourTurn := ""
+ if m.turn%2 == 0 && m.peer == "peer-2" || m.turn%2 == 1 && m.peer == "peer-1" {
+ yourTurn = "[YOUR TURN]"
}
- content = fmt.Sprintf("You're playing versus %s", otherPlayer)
+
+ content = fmt.Sprintf("%s vs %s\n%s\n\n%s\n%s",
+ m.game.Player1.Username,
+ m.game.Player2.Username,
+ lipgloss.NewStyle().Foreground(highlightColor).Render(yourTurn),
+ m.chessGame.Position().Board().Draw(),
+ m.chessGame.String(),
+ )
}
windowContent := m.buildWindowContent(content, formWidth)
@@ -128,6 +156,15 @@ 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.RandomMove):
+ if m.turn%2 == 0 && m.peer == "peer-2" || m.turn%2 == 1 && m.peer == "peer-1" {
+ moves := m.chessGame.ValidMoves()
+ move := moves[rand.Intn(len(moves))]
+ m.network.Server.Send(network.NetworkID(m.peer), []byte(move.String()))
+ m.chessGame.MoveStr(move.String())
+ m.turn++
+ }
+ return m, nil
case key.Matches(msg, m.keys.Quit):
return m, tea.Quit
}
@@ -145,6 +182,10 @@ func (m GameModel) buildWindowContent(content string, formWidth int) string {
}
func (m GameModel) renderNavigationButtons() string {
+ randomMoveKey := fmt.Sprintf("%s %s",
+ altCodeStyle.Render(m.keys.RandomMove.Help().Key),
+ m.keys.RandomMove.Help().Desc)
+
logoutKey := fmt.Sprintf("%s %s",
altCodeStyle.Render(m.keys.GoLogout.Help().Key),
m.keys.GoLogout.Help().Desc)
@@ -155,6 +196,7 @@ func (m GameModel) renderNavigationButtons() string {
return lipgloss.JoinVertical(
lipgloss.Left,
+ randomMoveKey,
logoutKey,
quitKey,
)
@@ -162,7 +204,7 @@ func (m GameModel) renderNavigationButtons() string {
func (m *GameModel) handleGetGameResponse(msg database.Game) (tea.Model, tea.Cmd) {
m.game = &msg
- if m.peer == "peer-1" {
+ if m.peer == "peer-2" {
m.network.Peer = msg.IP2
} else {
m.network.Peer = msg.IP1
@@ -193,7 +235,7 @@ func (m *GameModel) getGame() tea.Cmd {
}
// Establish peer connection
- if m.peer == "peer-1" {
+ if m.peer == "peer-2" {
if game.IP2 != "" {
remote := game.IP2
go m.network.Server.AddPeer("peer-2", remote)
@@ -208,3 +250,15 @@ func (m *GameModel) getGame() tea.Cmd {
return game
}
}
+
+func (m *GameModel) getMoves() tea.Cmd {
+ m.network.Server.OnReceiveFn = func(msg network.Message) {
+ moveStr := string(msg.Payload)
+ m.incomingMoves <- moveStr
+ }
+
+ return func() tea.Msg {
+ move := <-m.incomingMoves
+ return ChessMoveMsg(move)
+ }
+}
diff --git a/pkg/ui/views/play.go b/pkg/ui/views/play.go
index cf1f6ca..3997a88 100644
--- a/pkg/ui/views/play.go
+++ b/pkg/ui/views/play.go
@@ -38,8 +38,6 @@ A B C D E F G H
type PlayModelPage int
-var start = make(chan int)
-
const (
LandingPage PlayModelPage = iota
InsertCodePage
@@ -97,6 +95,8 @@ var defaultPlayKeyMap = playKeyMap{
),
}
+type StartGameMsg struct{}
+
type PlayModel struct {
// UI dimensions
width int
@@ -159,12 +159,6 @@ func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, exit
}
- select {
- case <-start:
- return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-1", m.currentGameId, m.network))
- default:
- }
-
switch msg := msg.(type) {
case tea.WindowSizeMsg:
return m.handleWindowSize(msg)
@@ -176,6 +170,8 @@ func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.handleGameResponse(msg)
case []database.Game:
return m.handleGamesResponse(msg)
+ case StartGameMsg:
+ return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-2", m.currentGameId, m.network))
case error:
return m.handleError(msg)
}
@@ -269,10 +265,17 @@ func (m *PlayModel) handlePlayResponse(msg playResponse) (tea.Model, tea.Cmd) {
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 {
- start <- 1
+ close(callbackCompleted)
return nil
}, logger)
+
+ return m, func() tea.Msg {
+ <-callbackCompleted
+ return StartGameMsg{}
+ }
}
return m, nil
@@ -292,7 +295,7 @@ func (m *PlayModel) handleGameResponse(msg database.Game) (tea.Model, tea.Cmd) {
return nil
}, logger)
- return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-2", m.game.ID, network))
+ return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, "peer-1", m.game.ID, network))
}
return m, nil
}