summaryrefslogtreecommitdiff
path: root/pkg/ui
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2025-04-17 16:11:34 +0200
committerSanto Cariotti <santo@dcariotti.me>2025-04-17 16:11:34 +0200
commit95da822f025b544f700729afc676ba0f3c981154 (patch)
tree89b07c3a4e8797e67cb08e8f6603b7e7eae3b0e1 /pkg/ui
parent56c7e99b59a52f19598d33a46aea0516bd4d609f (diff)
Outcome for a game
Diffstat (limited to 'pkg/ui')
-rw-r--r--pkg/ui/views/game.go75
-rw-r--r--pkg/ui/views/game_api.go47
-rw-r--r--pkg/ui/views/game_moves.go17
3 files changed, 122 insertions, 17 deletions
diff --git a/pkg/ui/views/game.go b/pkg/ui/views/game.go
index eb65c96..d545fcb 100644
--- a/pkg/ui/views/game.go
+++ b/pkg/ui/views/game.go
@@ -93,8 +93,12 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m, cmd = m.handleChessMoveMsg(msg)
cmds = append(cmds, cmd)
case database.Game:
- m = m.handleDatabaseGameMsg(msg)
- cmds = append(cmds, m.updateMovesListCmd())
+ m, cmd = m.handleDatabaseGameMsg(msg)
+ cmds = append(cmds, cmd, m.updateMovesListCmd())
+ case EndGameMsg:
+ return m, nil
+ case error:
+ m.err = msg
}
if m.isMyTurn() {
@@ -105,10 +109,20 @@ func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
selectedItem := m.availableMovesList.SelectedItem()
if selectedItem != nil {
moveStr := strings.Replace(selectedItem.(item).Title(), " → ", "", 1)
- m.network.Server.Send(network.NetworkID(m.peer), []byte(moveStr))
- m.chessGame.MoveStr(moveStr)
- m.turn++
+ moveStr = strings.Replace(moveStr, " ", "", 1)
+ err := m.chessGame.MoveStr(moveStr)
+ if err != nil {
+ m.err = err
+ } else {
+ m.turn++
+ m.network.Server.Send(network.NetworkID(m.peer), []byte(moveStr))
+ m.err = nil
+ }
cmds = append(cmds, m.getMoves(), m.updateMovesListCmd())
+
+ if m.chessGame.Outcome() != chess.NoOutcome {
+ cmds = append(cmds, m.endGame())
+ }
}
}
}
@@ -140,20 +154,57 @@ func (m GameModel) View() string {
var availableMovesListView string
- if m.isMyTurn() {
- m.availableMovesList.SetSize(listWidth, listHeight-2)
- availableMovesListView = listStyle.Render(m.availableMovesList.View())
+ if m.game.Outcome == chess.NoOutcome.String() {
+ if m.isMyTurn() {
+ m.availableMovesList.SetSize(listWidth, listHeight-2)
+ availableMovesListView = listStyle.Render(m.availableMovesList.View())
+ } else {
+ availableMovesListView = listStyle.Render(lipgloss.Place(listWidth, listHeight, lipgloss.Center, lipgloss.Center, "Wait your turn"))
+ }
} else {
- availableMovesListView = listStyle.Render(lipgloss.Place(listWidth, listHeight, lipgloss.Center, lipgloss.Center, "Wait your turn"))
+ var outcome string
+ switch m.game.Outcome {
+ case "1-0":
+ outcome = "White won"
+ if m.peer == "peer-2" {
+ outcome += " (YOU)"
+ }
+ case "0-1":
+ outcome = "Black won"
+ if m.peer == "peer-1" {
+ outcome += " (YOU)"
+ }
+ case "1/2-1/2":
+ outcome = "Draw"
+ default:
+ outcome = "NoOutcome"
+ }
+
+ availableMovesListView = listStyle.Render(
+ lipgloss.JoinVertical(
+ lipgloss.Left,
+ lipgloss.NewStyle().Background(highlightColor).Foreground(lipgloss.Color("230")).Padding(0, 1).MarginBottom(1).Render("Result"),
+ outcome,
+ m.game.Outcome,
+ ),
+ )
}
var movesListStr string
for i, move := range m.chessGame.Moves() {
+ s1 := move.S1().String()
+ s2 := move.S2().String()
+ var promo string
+
+ if move.Promo().String() != "" {
+ promo = " " + move.Promo().String()
+ }
+
if i%2 == 0 {
- movesListStr += altCodeStyle.Render(fmt.Sprintf("[%d]", i/2)) + fmt.Sprintf(" %s → %s", move.S1().String(), move.S2().String())
+ movesListStr += altCodeStyle.Render(fmt.Sprintf("[%d]", i/2)) + fmt.Sprintf(" %s → %s%s", s1, s2, promo)
} else {
- movesListStr += fmt.Sprintf(", %s → %s\n", move.S1().String(), move.S2().String())
+ movesListStr += fmt.Sprintf(", %s → %s%s\n", s1, s2, promo)
}
}
@@ -173,7 +224,7 @@ func (m GameModel) View() string {
content := lipgloss.JoinVertical(
lipgloss.Center,
- lipgloss.NewStyle().Foreground(lipgloss.Color("#f1c40f")).Render(fmt.Sprintf("%s vs %s", m.game.Player1.Username, m.game.Player2.Username)),
+ lipgloss.NewStyle().Foreground(lipgloss.Color("#f1c40f")).Render(fmt.Sprintf("♔ %s vs ♚ %s", m.game.Player1.Username, m.game.Player2.Username)),
lipgloss.JoinHorizontal(
lipgloss.Top,
availableMovesListView,
diff --git a/pkg/ui/views/game_api.go b/pkg/ui/views/game_api.go
index 5d4edf9..75aa0a1 100644
--- a/pkg/ui/views/game_api.go
+++ b/pkg/ui/views/game_api.go
@@ -7,16 +7,26 @@ import (
"github.com/boozec/rahanna/internal/api/database"
tea "github.com/charmbracelet/bubbletea"
+ "github.com/notnil/chess"
)
-func (m GameModel) handleDatabaseGameMsg(msg database.Game) GameModel {
+func (m GameModel) handleDatabaseGameMsg(msg database.Game) (GameModel, tea.Cmd) {
m.game = &msg
if m.peer == "peer-2" {
m.network.Peer = msg.IP2
} else {
m.network.Peer = msg.IP1
}
- return m
+
+ var cmd tea.Cmd
+
+ if m.game.Outcome != chess.NoOutcome.String() {
+ cmd = func() tea.Msg {
+ return EndGameMsg{}
+ }
+ }
+
+ return m, cmd
}
func (m *GameModel) getGame() tea.Cmd {
@@ -57,3 +67,36 @@ func (m *GameModel) getGame() tea.Cmd {
return game
}
}
+
+type EndGameMsg struct{}
+
+func (m *GameModel) endGame() tea.Cmd {
+ return func() tea.Msg {
+ var game database.Game
+
+ // Get authorization token
+ authorization, err := getAuthorizationToken()
+ if err != nil {
+ return err
+ }
+
+ // Prepare request payload
+ payload, err := json.Marshal(map[string]string{
+ "outcome": m.chessGame.Outcome().String(),
+ })
+
+ // Send API request
+ url := fmt.Sprintf("%s/play/%d/end", os.Getenv("API_BASE"), m.currentGameID)
+ resp, err := sendAPIRequest("POST", url, payload, authorization)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if err := json.NewDecoder(resp.Body).Decode(&game); err != nil {
+ return err
+ }
+
+ return game
+ }
+}
diff --git a/pkg/ui/views/game_moves.go b/pkg/ui/views/game_moves.go
index de2ef94..2fa7c9e 100644
--- a/pkg/ui/views/game_moves.go
+++ b/pkg/ui/views/game_moves.go
@@ -6,6 +6,7 @@ import (
"github.com/boozec/rahanna/internal/network"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
+ "github.com/notnil/chess"
)
// UpdateMovesListMsg is a message to update the moves list
@@ -44,9 +45,13 @@ func (m GameModel) handleUpdateMovesListMsg() GameModel {
if m.isMyTurn() && m.game != nil {
var items []list.Item
for _, move := range m.chessGame.ValidMoves() {
+ var promo string
+ if move.Promo().String() != "" {
+ promo = " " + move.Promo().String()
+ }
items = append(
items,
- item{title: fmt.Sprintf("%s → %s", move.S1().String(), move.S2().String())},
+ item{title: fmt.Sprintf("%s → %s%s", move.S1().String(), move.S2().String(), promo)},
)
}
m.availableMovesList.SetItems(items)
@@ -60,12 +65,18 @@ func (m GameModel) handleUpdateMovesListMsg() GameModel {
}
func (m GameModel) handleChessMoveMsg(msg ChessMoveMsg) (GameModel, tea.Cmd) {
- m.turn++
err := m.chessGame.MoveStr(string(msg))
+ cmds := []tea.Cmd{m.getMoves(), m.updateMovesListCmd()}
if err != nil {
m.err = err
} else {
+ m.turn++
m.err = nil
}
- return m, tea.Batch(m.getMoves(), m.updateMovesListCmd())
+
+ if m.chessGame.Outcome() != chess.NoOutcome {
+ cmds = append(cmds, m.endGame())
+ }
+
+ return m, tea.Batch(cmds...)
}