Skip to content

Commit

Permalink
feat: auto reveal (#76)
Browse files Browse the repository at this point in the history
* chore: retract vote (not reveal)

* chore: suite FakeIssue method

* feat: game auto reveal

* feat: auto reveal view

* chore: makefile phony
  • Loading branch information
igor-sirotin authored Jul 14, 2024
1 parent 88825ab commit ef759ff
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 69 deletions.
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
.PHONY: build run generate test
.PHONY:
build
build-all
run
generate
test
lint
lint-fix
demo

build: generate
@go build -v -o 2sp ./cmd/2sp
Expand Down
8 changes: 8 additions & 0 deletions internal/testcommon/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go.uber.org/zap"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/pkg/protocol"
)

type Suite struct {
Expand Down Expand Up @@ -46,3 +47,10 @@ func (s *Suite) FakePayload() ([]byte, []byte) {

return payload, jsonPayload
}

func (s *Suite) FakeIssue() *protocol.Issue {
issue := &protocol.Issue{}
err := gofakeit.Struct(&issue)
s.Require().NoError(err)
return issue
}
18 changes: 9 additions & 9 deletions internal/view/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@

Author: @Alice [LanguageChange] [v2] [Proposal]
Assignee: @Bob [NeedsInvestigation]

╭───────┬───────────┬─────────┬───────┬──────╮
│ Alice │ Bob (You) │ Charlie │ David │ Erin │ Recommended: 8
├───────┼───────────┼─────────┼───────┼──────┤ Acceptable: ✓
│ 8 │ 8 │ 5 │ 5 │ 8 │ > Not bad.
│ Alice │ Bob (You) │ Charlie │ David │ Erin │
├───────┼───────────┼─────────┼───────┼──────┤
│ 8 │ 8 │ 5 │ 5 │ 8 │ Revealing in 3.0
╰───────┴───────────┴─────────┴───────┴──────╯
╭───╮
╭───╮ ╭───╮ ╭───╮ ╭───╮ │ 8 │ ╭────╮ ╭────╮ ╭───
│ 1 │ │ 2 │ │ 3 │ │ 5 │ ╰───╯ │ 13 │ │ 21 │ │ 34
╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰────╯ ╰────╯ ╰───
╭───╮ ╭───╮ ╭───╮ ╭───╮ │ 8 │ ╭────╮ ╭────╮ ╭───╮
│ 1 │ │ 2 │ │ 3 │ │ 5 │ ╰───╯ │ 13 │ │ 21 │ │ ?
╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰────╯ ╰────╯ ╰───╯
^

Use [←] and [→] arrows to select a card and press [Enter]
[Tab] To switch to issues list view
[C] Switch to command mode [Q] Leave room [E] Exit [H] Help
Expand Down Expand Up @@ -192,7 +192,7 @@ Therefore the card can be in one of these 4 states:
</tr>
</table>
For example for a Fibbonacci deck
For example for a Fibonacci deck
```shell
Your vote:
Expand Down
55 changes: 28 additions & 27 deletions internal/view/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,47 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors"
"golang.org/x/exp/slices"

"github.com/six78/2-story-points-cli/internal/view/commands"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
"github.com/six78/2-story-points-cli/pkg/game"
"github.com/six78/2-story-points-cli/pkg/protocol"
"golang.org/x/exp/slices"
)

type Action string

const (
Rename Action = "rename"
New Action = "new"
Join Action = "join"
Exit Action = "exit"
Vote Action = "vote"
Unvote Action = "unvote"
Deal Action = "deal"
Add Action = "add"
Reveal Action = "reveal"
Finish Action = "finish"
Deck Action = "deck"
Select Action = "select"
Rename Action = "rename"
New Action = "new"
Join Action = "join"
Exit Action = "exit"
Vote Action = "vote"
Retract Action = "retract"
Deal Action = "deal"
Add Action = "add"
Reveal Action = "reveal"
Finish Action = "finish"
Deck Action = "deck"
Select Action = "select"
)

type actionFunc func(m *model, args []string) tea.Cmd

var actions = map[Action]actionFunc{
Rename: runRenameAction,
Vote: runVoteAction,
Unvote: runUnvoteAction,
Deal: runDealAction,
Add: runAddAction,
New: runNewAction,
Join: runJoinAction,
Exit: runExitAction,
Reveal: runRevealAction,
Finish: runFinishAction,
Deck: runDeckAction,
Select: runSelectAction,
Rename: runRenameAction,
Vote: runVoteAction,
Retract: runRetractAction,
Deal: runDealAction,
Add: runAddAction,
New: runNewAction,
Join: runJoinAction,
Exit: runExitAction,
Reveal: runRevealAction,
Finish: runFinishAction,
Deck: runDeckAction,
Select: runSelectAction,
}

func processPlayerNameInput(m *model, playerName string) tea.Cmd {
Expand Down Expand Up @@ -93,9 +94,9 @@ func runVoteAction(m *model, args []string) tea.Cmd {
}
}

func runUnvoteAction(m *model, args []string) tea.Cmd {
func runRetractAction(m *model, args []string) tea.Cmd {
return func() tea.Msg {
return commands.PublishVote(m.game, "")()
return commands.RetractVote(m.game)()
}
}

Expand Down
14 changes: 14 additions & 0 deletions internal/view/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors"

"github.com/six78/2-story-points-cli/internal/transport"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
Expand Down Expand Up @@ -99,6 +100,19 @@ func PublishVote(game *game.Game, vote protocol.VoteValue) tea.Cmd {
}
}

func RetractVote(game *game.Game) tea.Cmd {
return func() tea.Msg {
err := game.RetractVote()
if err != nil {
return messages.NewErrorMessage(err)
}
// TODO: Send err=nil ErrorMessage here
return messages.MyVote{
Result: game.MyVote(),
}
}
}

func FinishVoting(game *game.Game, result protocol.VoteValue) tea.Cmd {
return func() tea.Msg {
err := game.Finish(result)
Expand Down
2 changes: 0 additions & 2 deletions internal/view/components/hintview/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ func (m Model) View() string {
}

return lipgloss.JoinVertical(lipgloss.Top,
"",
headerStyle.Render("Recommended:")+""+voteview.Render(m.hint.Value),
headerStyle.Render("Acceptable:")+" "+renderAcceptanceIcon(m.hint.Acceptable),
headerStyle.Render(">")+" "+textStyle.Render(m.hint.Description),
"",
)
}

Expand Down
2 changes: 0 additions & 2 deletions internal/view/components/hintview/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ func TestUpdateAcceptableVote(t *testing.T) {
}

expectedLines := []string{
"",
"Recommended: " + string(issue.Hint.Value),
"Acceptable: " + expectedAcceptableIcon,
"> " + issue.Hint.Description,
"",
}

lines := strings.Split(model.View(), "\n")
Expand Down
3 changes: 1 addition & 2 deletions internal/view/components/issueview/issue_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ var (
primaryStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F0F0F0"))
secondaryStyle = lipgloss.NewStyle().Foreground(config.ForegroundShadeColor)
hyperlinkStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#648EF8")).Underline(true)
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF1744"))
)

const (
Expand Down Expand Up @@ -146,7 +145,7 @@ func (m *Model) renderTitle(info *issueInfo) string {
}

if info.err != nil {
return errorStyle.Render(fmt.Sprintf("[%s]", info.err.Error()))
return secondaryStyle.Render(fmt.Sprintf("[%s]", info.err.Error()))
}

if info.title == nil {
Expand Down
47 changes: 47 additions & 0 deletions internal/view/components/votestate/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package votestate

import (
"fmt"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/six78/2-story-points-cli/internal/config"
"github.com/six78/2-story-points-cli/internal/view/messages"
)

var style = lipgloss.NewStyle().Foreground(config.ForegroundShadeColor)

type Model struct {
duration time.Duration
start time.Time
}

func (m Model) Init() tea.Cmd {
return nil
}

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case messages.AutoRevealScheduled:
m.start = time.Now()
m.duration = msg.Duration
case messages.AutoRevealCancelled:
m.start = time.Time{}
m.duration = 0
}

return m, nil
}

func (m Model) View() string {
if m.start.IsZero() {
return ""
}
left := (m.duration - time.Since(m.start)).Seconds()
if left == 0 {
return style.Render("Revealing votes...")
}
return style.Render(fmt.Sprintf("Revealing in %.1f", left))
}
9 changes: 9 additions & 0 deletions internal/view/messages/messages.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package messages

import (
"time"

"github.com/six78/2-story-points-cli/internal/transport"
"github.com/six78/2-story-points-cli/internal/view/states"
"github.com/six78/2-story-points-cli/pkg/protocol"
Expand Down Expand Up @@ -59,3 +61,10 @@ type MyVote struct {

type EnableEnterKey struct {
}

type AutoRevealScheduled struct {
Duration time.Duration
}

type AutoRevealCancelled struct {
}
32 changes: 22 additions & 10 deletions internal/view/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/six78/2-story-points-cli/internal/view/components/playersview"
"github.com/six78/2-story-points-cli/internal/view/components/shortcutsview"
"github.com/six78/2-story-points-cli/internal/view/components/userinput"
"github.com/six78/2-story-points-cli/internal/view/components/votestate"
"github.com/six78/2-story-points-cli/internal/view/components/wakustatusview"
"github.com/six78/2-story-points-cli/internal/view/messages"
"github.com/six78/2-story-points-cli/internal/view/states"
Expand All @@ -46,16 +47,18 @@ type model struct {
connectionStatus transport.ConnectionStatus

// UI components state
commandMode bool
roomViewState states.RoomView
errorView errorview.Model
playersView playersview.Model
hintView hintview.Model
shortcutsView shortcutsview.Model
wakuStatusView wakustatusview.Model
deckView deckview.Model
issueView issueview.Model
issuesListView issuesview.Model
commandMode bool
roomViewState states.RoomView
errorView errorview.Model
playersView playersview.Model
hintView hintview.Model
shortcutsView shortcutsview.Model
wakuStatusView wakustatusview.Model
deckView deckview.Model
issueView issueview.Model
issuesListView issuesview.Model
voteStateView votestate.Model

gameEventHandler eventhandler.Model[game.Event, interface{}]
transportEventHandler eventhandler.Model[transport.ConnectionStatus, messages.ConnectionStatus]

Expand Down Expand Up @@ -95,6 +98,7 @@ func InitialModel(game *game.Game, transport transport.Service) model {
deckView: deckView,
issueView: issueview.New(),
issuesListView: issuesview.New(),
voteStateView: votestate.Model{},
// Other
disableEnterKey: false,
disableEnterRestart: nil,
Expand All @@ -120,6 +124,7 @@ func (m model) Init() tea.Cmd {
m.deckView.Init(),
m.issueView.Init(),
m.issuesListView.Init(),
m.voteStateView.Init(),
commands.InitializeApp(m.game, m.transport),
)
}
Expand Down Expand Up @@ -286,6 +291,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.deckView = m.deckView.Update(msg)
m.issueView, cmds.IssueViewCommand = m.issueView.Update(msg)
m.issuesListView, cmds.IssuesListViewCommand = m.issuesListView.Update(msg)
m.voteStateView, _ = m.voteStateView.Update(msg)
m.gameEventHandler, cmds.GameEventHandlerCommand = m.gameEventHandler.Update(msg)
m.transportEventHandler, cmds.TransportEventHandlerCommand = m.transportEventHandler.Update(msg)

Expand Down Expand Up @@ -400,6 +406,12 @@ func gameEventToMessage(event game.Event) interface{} {
if state, ok := event.Data.(*protocol.State); ok {
return messages.GameStateMessage{State: state}
}
case game.EventAutoRevealScheduled:
if duration, ok := event.Data.(time.Duration); ok {
return messages.AutoRevealScheduled{Duration: duration}
}
case game.EventAutoRevealCancelled:
return messages.AutoRevealCancelled{}
default:
return nil
}
Expand Down
9 changes: 8 additions & 1 deletion internal/view/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,17 @@ func (m model) renderRoomCurrentIssueView() string {
)
}

playersView := m.playersView.View()
if m.gameState.VotesRevealed {
playersView = lipgloss.JoinHorizontal(lipgloss.Center, playersView, " ", m.hintView.View())
} else if m.game.IsDealer() && m.gameState.AllPlayersVoted() {
playersView = lipgloss.JoinHorizontal(0.75, playersView, " ", m.voteStateView.View())
}

return lipgloss.JoinVertical(lipgloss.Top,
m.issueView.View(),
"",
lipgloss.JoinHorizontal(lipgloss.Left, m.playersView.View(), " ", m.hintView.View()),
playersView,
m.deckView.View(),
)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/game/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type configuration struct {
OnlineMessagePeriod time.Duration
StateMessagePeriod time.Duration
PublishStateLoopEnabled bool
AutoRevealEnabled bool
AutoRevealDelay time.Duration
}

var defaultConfig = configuration{
Expand All @@ -16,4 +18,6 @@ var defaultConfig = configuration{
OnlineMessagePeriod: 5 * time.Second,
StateMessagePeriod: 30 * time.Second,
PublishStateLoopEnabled: true,
AutoRevealEnabled: true,
AutoRevealDelay: 2 * time.Second,
}
Loading

0 comments on commit ef759ff

Please sign in to comment.