diff options
Diffstat (limited to 'pkg/ui/views')
-rw-r--r-- | pkg/ui/views/game.go | 15 | ||||
-rw-r--r-- | pkg/ui/views/game_api.go | 14 | ||||
-rw-r--r-- | pkg/ui/views/game_moves.go | 11 | ||||
-rw-r--r-- | pkg/ui/views/game_restore.go | 54 | ||||
-rw-r--r-- | pkg/ui/views/play.go | 3 | ||||
-rw-r--r-- | pkg/ui/views/play_api.go | 22 | ||||
-rw-r--r-- | pkg/ui/views/play_keymap.go | 31 |
7 files changed, 137 insertions, 13 deletions
diff --git a/pkg/ui/views/game.go b/pkg/ui/views/game.go index 3742101..85e879c 100644 --- a/pkg/ui/views/game.go +++ b/pkg/ui/views/game.go @@ -24,6 +24,7 @@ type GameModel struct { keys gameKeyMap // Game state + restore bool currentGameID int game *database.Game network *multiplayer.GameNetwork @@ -34,7 +35,7 @@ type GameModel struct { } // NewGameModel creates a new GameModel. -func NewGameModel(width, height int, currentGameID int, network *multiplayer.GameNetwork) GameModel { +func NewGameModel(width, height int, currentGameID int, network *multiplayer.GameNetwork, restore bool) GameModel { listDelegate := list.NewDefaultDelegate() listDelegate.ShowDescription = false listDelegate.Styles.SelectedTitle = lipgloss.NewStyle(). @@ -59,6 +60,7 @@ func NewGameModel(width, height int, currentGameID int, network *multiplayer.Gam incomingMoves: make(chan multiplayer.GameMove), turn: 0, availableMovesList: moveList, + restore: restore, } } @@ -89,6 +91,13 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case ChessMoveMsg: m, cmd = m.handleChessMoveMsg(msg) cmds = append(cmds, cmd) + case SendRestoreMsg: + cmd = m.handleSendRestoreMsg() + cmds = append(cmds, cmd) + case RestoreMoves: + cmd = m.handleRestoreMoves(msg) + cmds = append(cmds, cmd) + case database.Game: m, cmd = m.handleDatabaseGameMsg(msg) cmds = append(cmds, cmd, m.updateMovesListCmd()) @@ -104,6 +113,10 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.err = m.network.Close() + case RestoreGameMsg: + m.network.Send([]byte("restore"), []byte(m.network.Me())) + m.restore = false + case error: m.err = msg } diff --git a/pkg/ui/views/game_api.go b/pkg/ui/views/game_api.go index e2ef280..3c649f9 100644 --- a/pkg/ui/views/game_api.go +++ b/pkg/ui/views/game_api.go @@ -21,17 +21,21 @@ func (m GameModel) handleDatabaseGameMsg(msg database.Game) (GameModel, tea.Cmd) if m.network.Me() == m.playerPeer(1) { if m.game.IP2 != "" { remote := m.game.IP2 - go m.network.AddPeer(m.playerPeer(2), remote) + m.network.AddPeer(m.playerPeer(2), remote) } } else { if m.game.IP1 != "" { remote := m.game.IP1 - go m.network.AddPeer(m.playerPeer(1), remote) + m.network.AddPeer(m.playerPeer(1), remote) } } } - if m.game.Outcome != chess.NoOutcome.String() { + if m.restore { + cmd = func() tea.Msg { + return RestoreGameMsg{} + } + } else if m.game.Outcome != chess.NoOutcome.String() { cmd = func() tea.Msg { return EndGameMsg{} } @@ -62,6 +66,8 @@ func (m *GameModel) getGame() tea.Cmd { return nil } + m.game = &game + return game } } @@ -70,6 +76,8 @@ type EndGameMsg struct { abandoned bool } +type RestoreGameMsg struct{} + func (m *GameModel) endGame(outcome string) tea.Cmd { return func() tea.Msg { var game database.Game diff --git a/pkg/ui/views/game_moves.go b/pkg/ui/views/game_moves.go index ea1fa02..32f440f 100644 --- a/pkg/ui/views/game_moves.go +++ b/pkg/ui/views/game_moves.go @@ -35,10 +35,17 @@ func (m *GameModel) getMoves() tea.Cmd { return func() tea.Msg { move := <-m.incomingMoves - if multiplayer.MoveType(string(move.Type)) == multiplayer.AbandonGameMessage { + + switch multiplayer.MoveType(string(move.Type)) { + case multiplayer.AbandonGameMessage: return EndGameMsg{abandoned: true} + case multiplayer.RestoreGameMessage: + return SendRestoreMsg{} + case multiplayer.RestoreAckGameMessage: + return RestoreMoves(string(move.Payload)) + default: + return ChessMoveMsg(string(move.Payload)) } - return ChessMoveMsg(string(move.Payload)) } } diff --git a/pkg/ui/views/game_restore.go b/pkg/ui/views/game_restore.go new file mode 100644 index 0000000..b2e647c --- /dev/null +++ b/pkg/ui/views/game_restore.go @@ -0,0 +1,54 @@ +package views + +import ( + "fmt" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +// Catch for `RestoreGameMessage` message from multiplayer +type SendRestoreMsg struct{} + +// Catch for `RestoreAckGameMessage` message from multiplayer +type RestoreMoves string + +// For `RestoreGameMessage` from multiplayer it fixes the peer with the new +// address and sends back an ACK to the peer' sender +func (m GameModel) handleSendRestoreMsg() tea.Cmd { + if m.network.Me() == m.playerPeer(1) { + _ = m.getGame()() + remote := m.game.IP2 + m.network.AddPeer(m.playerPeer(2), remote) + } else { + _ = m.getGame()() + remote := m.game.IP1 + m.network.AddPeer(m.playerPeer(1), remote) + } + + // FIXME: add a loading modal + time.Sleep(2 * time.Second) + + payload := "" + + for _, move := range m.chessGame.Moves() { + payload += fmt.Sprintf("%s\n", move.String()) + } + + m.err = m.network.Send([]byte("restore-ack"), []byte(payload)) + + return nil +} + +// Restores the moves for `m.chessGame` +func (m *GameModel) handleRestoreMoves(msg RestoreMoves) tea.Cmd { + moves := strings.Split(string(msg), "\n") + for _, move := range moves { + m.chessGame.MoveStr(move) + } + + m.turn = len(moves) - 1 + cmds := []tea.Cmd{m.getMoves(), m.updateMovesListCmd()} + return tea.Batch(cmds...) +} diff --git a/pkg/ui/views/play.go b/pkg/ui/views/play.go index 20e2ebb..3d75a90 100644 --- a/pkg/ui/views/play.go +++ b/pkg/ui/views/play.go @@ -46,6 +46,7 @@ type PlayModel struct { game *database.Game network *multiplayer.GameNetwork games []database.Game + gameToRestore *database.Game } // NewPlayModel creates a new play model instance @@ -89,7 +90,7 @@ func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.userID, m.err = getUserID() return m.handleGamesResponse(msg) case StartGameMsg: - return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, m.currentGameId, m.network)) + return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, m.currentGameId, m.network, m.gameToRestore != nil)) case error: return m.handleError(msg) } diff --git a/pkg/ui/views/play_api.go b/pkg/ui/views/play_api.go index c098930..bf9b08a 100644 --- a/pkg/ui/views/play_api.go +++ b/pkg/ui/views/play_api.go @@ -46,8 +46,8 @@ func (m *PlayModel) handlePlayResponse(msg playResponse) (tea.Model, tea.Cmd) { logger, _ := logger.GetLogger() callbackCompleted := make(chan bool) - m.network = multiplayer.NewGameNetwork(fmt.Sprintf("%s-1", m.playName), fmt.Sprintf("%s:%d", msg.Ok.IP, msg.Ok.Port), func(net.Conn) error { - callbackCompleted <- true + m.network = multiplayer.NewGameNetwork(fmt.Sprintf("%s-1", m.playName), fmt.Sprintf("%s:%d", msg.Ok.IP, msg.Ok.Port), p2p.DefaultHandshake, func(net.Conn) error { + close(callbackCompleted) return nil }, logger) @@ -59,19 +59,31 @@ func (m *PlayModel) handlePlayResponse(msg playResponse) (tea.Model, tea.Cmd) { 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, ":") + + var ip []string + var localID string + + if m.game.LastPlayer == 2 { + ip = strings.Split(m.game.IP2, ":") + localID = fmt.Sprintf("%s-2", m.game.Name) + } else { + ip = strings.Split(m.game.IP1, ":") + localID = fmt.Sprintf("%s-1", m.game.Name) + } + if len(ip) == 2 { localIP := ip[0] localPort, _ := strconv.ParseInt(ip[1], 10, 32) logger, _ := logger.GetLogger() - network := multiplayer.NewGameNetwork(fmt.Sprintf("%s-2", m.game.Name), fmt.Sprintf("%s:%d", localIP, localPort), p2p.DefaultHandshake, logger) + network := multiplayer.NewGameNetwork(localID, fmt.Sprintf("%s:%d", localIP, localPort), p2p.DefaultHandshake, p2p.DefaultHandshake, logger) - return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, m.game.ID, network)) + return m, SwitchModelCmd(NewGameModel(m.width, m.height+1, m.game.ID, network, m.gameToRestore != nil)) } return m, nil } diff --git a/pkg/ui/views/play_keymap.go b/pkg/ui/views/play_keymap.go index d5ccc9c..200f427 100644 --- a/pkg/ui/views/play_keymap.go +++ b/pkg/ui/views/play_keymap.go @@ -1,11 +1,14 @@ package views import ( + "errors" "fmt" + "strconv" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/notnil/chess" ) type PlayModelPage int @@ -20,6 +23,7 @@ const ( type playKeyMap struct { EnterNewGame key.Binding StartNewGame key.Binding + RestoreGame key.Binding GoLogout key.Binding Quit key.Binding NextPage key.Binding @@ -36,6 +40,10 @@ var defaultPlayKeyMap = playKeyMap{ key.WithKeys("alt+s", "alt+S"), key.WithHelp("Alt+S", "Start a new play"), ), + RestoreGame: key.NewBinding( + key.WithKeys("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"), + key.WithHelp("[0-9]", "Restore a game"), + ), GoLogout: key.NewBinding( key.WithKeys("alt+Q", "alt+q"), key.WithHelp("Alt+Q", "Logout"), @@ -60,7 +68,6 @@ func (m PlayModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, 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): @@ -70,6 +77,23 @@ func (m PlayModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, m.newGameCallback() } + case key.Matches(msg, m.keys.RestoreGame): + idx, err := strconv.Atoi(msg.String()) + m.err = err + if err == nil { + gameIndex := m.paginator.Page*m.paginator.PerPage + idx + if gameIndex < len(m.games) { + m.gameToRestore = &m.games[gameIndex] + if m.gameToRestore.Outcome != chess.NoOutcome.String() { + m.err = errors.New("this game is closed") + } else { + m.err = nil + m.namePrompt.SetValue(m.gameToRestore.Name) + return m, m.enterGame() + } + } + } + case key.Matches(msg, m.keys.GoLogout): return m, logout(m.width, m.height+1) @@ -107,6 +131,10 @@ func (m PlayModel) renderNavigationButtons() string { altCodeStyle.Render(m.keys.EnterNewGame.Help().Key), m.keys.EnterNewGame.Help().Desc) + restoreKey := fmt.Sprintf("%s %s", + altCodeStyle.Render(m.keys.RestoreGame.Help().Key), + m.keys.RestoreGame.Help().Desc) + startKey := fmt.Sprintf("%s %s", altCodeStyle.Render(m.keys.StartNewGame.Help().Key), m.keys.StartNewGame.Help().Desc) @@ -123,6 +151,7 @@ func (m PlayModel) renderNavigationButtons() string { lipgloss.Left, enterKey, startKey, + restoreKey, lipgloss.JoinHorizontal(lipgloss.Left, prevPageKey, " | ", nextPageKey), logoutKey, quitKey, |