diff options
author | Santo Cariotti <santo@dcariotti.me> | 2025-04-16 21:37:09 +0200 |
---|---|---|
committer | Santo Cariotti <santo@dcariotti.me> | 2025-04-16 21:37:09 +0200 |
commit | f60fadc54421c8e0aedb33e59270d4aa48e842d2 (patch) | |
tree | b15aac242a7f4548ebbe55dd5c4165da2cd3395e /pkg/ui/views | |
parent | ba4afeb4ee19c24b393ec21d374bdd752651c1a6 (diff) |
Send messages for randomm chess move
Diffstat (limited to 'pkg/ui/views')
-rw-r--r-- | pkg/ui/views/game.go | 88 | ||||
-rw-r--r-- | pkg/ui/views/play.go | 23 |
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 } |