Skip to content

Commit

Permalink
working status bar
Browse files Browse the repository at this point in the history
  • Loading branch information
dustin-ward committed Mar 7, 2024
1 parent b355e80 commit e00d826
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 16 deletions.
15 changes: 9 additions & 6 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/dustin-ward/termtyping/internal/character"
"github.com/dustin-ward/termtyping/internal/data"
"github.com/dustin-ward/termtyping/internal/stats"
"github.com/dustin-ward/termtyping/internal/statusbar"
"github.com/dustin-ward/termtyping/internal/styles"
)
Expand All @@ -29,21 +30,22 @@ type AppModel struct {

status_bar statusbar.StatusBarModel

num_typed int
num_correct int
stats *stats.Stats
}

// Number of words to use per each test
const NUM_WORDS = 20
const PUNC_CHANCE = 0.2
const CAPS_CHANCE = 0.2

func NewAppModel(init_state AppState) tea.Model {
func NewAppModel(init_state AppState, prev_stats *stats.Stats) tea.Model {
// Generate new body of text
text := data.GenText(NUM_WORDS, PUNC_CHANCE, CAPS_CHANCE)
return NewAppModelWithText(text, init_state)

return NewAppModelWithText(text, init_state, prev_stats)
}

func NewAppModelWithText(text string, init_state AppState) tea.Model {
func NewAppModelWithText(text string, init_state AppState, prev_stats *stats.Stats) tea.Model {
// Fill models array
chars := make([]character.CharacterModel, len(text))
for i, ch := range text {
Expand All @@ -58,7 +60,8 @@ func NewAppModelWithText(text string, init_state AppState) tea.Model {
chars: chars,
text: text,
pos: 0,
status_bar: statusbar.NewStatusBar(int(init_state)),
status_bar: statusbar.NewStatusBar(int(init_state), prev_stats),
stats: stats.NewStats(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions internal/app/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
tea "github.com/charmbracelet/bubbletea"
"github.com/dustin-ward/termtyping/internal/stats"
"github.com/dustin-ward/termtyping/internal/statusbar"
)

Expand All @@ -17,6 +18,7 @@ func defaultHandler(m AppModel, msg tea.Msg) (tea.Model, tea.Cmd) {

case "enter":
m.CurState = StateTyping
m.stats = stats.NewStats()
m.status_bar.CurState = statusbar.StateTyping
}
}
Expand Down
13 changes: 10 additions & 3 deletions internal/app/typing.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ func typingHandler(m AppModel, msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, cmd)

case "esc":
return NewAppModelWithText(m.text, StateDefault), func() tea.Msg { return ResetTextMsg{} }
// Escape will reset the state of each character while keeping the same text
return NewAppModelWithText(m.text, StateDefault, nil), func() tea.Msg { return ResetTextMsg{} }

case "enter":
// Enter key will generate new text
return NewAppModel(StateTyping), func() tea.Msg { return NewTextMsg{} }
return NewAppModel(StateTyping, nil), func() tea.Msg { return NewTextMsg{} }

default:
m.stats.NumKeypresses++

if keypress == m.chars[m.pos].Val {
m.stats.NumCorrect++

m.chars[m.pos].State = character.CorrectState
m.pos++
if m.pos < len(m.text) {
Expand All @@ -34,6 +40,7 @@ func typingHandler(m AppModel, msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.chars[m.pos].State = character.ActiveState
}

} else {
m.chars[m.pos].State = character.WrongState
}
Expand All @@ -42,7 +49,7 @@ func typingHandler(m AppModel, msg tea.Msg) (tea.Model, tea.Cmd) {

if m.pos == len(m.text) {
// All of the current text is completed. Reset
return NewAppModel(StateTyping), func() tea.Msg { return NewTextMsg{} }
return NewAppModel(StateTyping, m.stats.Finish()), func() tea.Msg { return NewTextMsg{} }
}

return m, tea.Batch(cmds...)
Expand Down
3 changes: 3 additions & 0 deletions internal/data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ func getWord(punctuation_chance, capital_chance float64) string {
return word
}

// Generate full body of text with num_words words.
// Punctuation will be added with a punc_chance*100% chance.
// Words will be capilized with a caps_chance*100% chance.
func GenText(num_words int, punc_chance, caps_chance float64) string {
text := ""
line_len := 0
Expand Down
35 changes: 35 additions & 0 deletions internal/stats/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package stats

import (
"time"
)

type Stats struct {
NumKeypresses int
NumCorrect int
LenText int
TimeStarted time.Time
TimeEnded time.Time
}

func NewStats() *Stats {
return &Stats{
NumKeypresses: 0,
NumCorrect: 0,
TimeStarted: time.Now(),
}
}

func (s *Stats) GetAccuracy() float64 {
return float64(s.NumCorrect) / float64(s.NumKeypresses)
}

// Avg word has 5 characters
func (s *Stats) GetWPM() float64 {
return (float64(s.NumCorrect) / 5.0) / s.TimeEnded.Sub(s.TimeStarted).Minutes()
}

func (s *Stats) Finish() *Stats {
s.TimeEnded = time.Now()
return s
}
70 changes: 70 additions & 0 deletions internal/statusbar/limits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package statusbar

import (
"github.com/charmbracelet/lipgloss"
"github.com/dustin-ward/termtyping/internal/styles"
)

// Colour gradient stops for acc metric
func getAccColour(acc float64) lipgloss.Style {
if acc < 0.01 {
return styles.HiddenText
}

c := styles.Accuracy30
if acc > 0.3 {
c = styles.Accuracy50
}
if acc > 0.5 {
c = styles.Accuracy70
}
if acc > 0.7 {
c = styles.Accuracy80
}
if acc > 0.8 {
c = styles.Accuracy90
}
if acc > 0.9 {
c = styles.Accuracy95
}
if acc > 0.95 {
c = styles.Accuracy98
}
if acc > 0.98 {
c = styles.Accuracy100
}

return lipgloss.NewStyle().Foreground(c)
}

// Colour gradient stops for wpm metric
func getWpmStyle(wpm float64) lipgloss.Style {
if wpm < 0.1 {
return styles.HiddenText
}

c := styles.Accuracy30
if wpm > 30.0 {
c = styles.Accuracy50
}
if wpm > 40.0 {
c = styles.Accuracy70
}
if wpm > 50.0 {
c = styles.Accuracy80
}
if wpm > 60.0 {
c = styles.Accuracy90
}
if wpm > 80.0 {
c = styles.Accuracy95
}
if wpm > 90.0 {
c = styles.Accuracy98
}
if wpm > 100.0 {
c = styles.Accuracy100
}

return lipgloss.NewStyle().Foreground(c)
}
25 changes: 19 additions & 6 deletions internal/statusbar/status_bar.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package statusbar

import (
"fmt"

tea "github.com/charmbracelet/bubbletea"
"github.com/dustin-ward/termtyping/internal/stats"
"github.com/dustin-ward/termtyping/internal/styles"
)

Expand All @@ -14,11 +17,13 @@ const (

type StatusBarModel struct {
CurState StatusBarState
Stats *stats.Stats
}

func NewStatusBar(init_state int) StatusBarModel {
func NewStatusBar(init_state int, init_stats *stats.Stats) StatusBarModel {
return StatusBarModel{
StatusBarState(init_state),
init_stats,
}
}

Expand All @@ -31,14 +36,22 @@ func (m StatusBarModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

func (m StatusBarModel) View() string {
var acc, wpm float64
if m.Stats != nil {
acc, wpm = m.Stats.GetAccuracy(), m.Stats.GetWPM()
}

acc_style := getAccColour(acc)
wpm_style := getWpmStyle(wpm)

switch m.CurState {
case StateDefault:
return "Press 'Enter' to begin..."
return "Press 'Enter' to begin... (esc to exit)"
case StateTyping:
return styles.HiddenText.Render("| ") +
"00.0%" +
styles.HiddenText.Render(" | ") +
"00.0wpm" +
return styles.HiddenText.Render("| Accuracy: ") +
acc_style.Render(fmt.Sprintf("%0.1f%%", acc*100)) +
styles.HiddenText.Render(" | Speed: ") +
wpm_style.Render(fmt.Sprintf("%0.1fwpm", wpm)) +
styles.HiddenText.Render(" |")
default:
return "ERROR"
Expand Down
9 changes: 9 additions & 0 deletions internal/styles/colours.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ var (
White = lipgloss.Color("#FFFFFF")
Red = lipgloss.Color("#FF0000")
Green = lipgloss.Color("#318A2D")

Accuracy30 = lipgloss.Color("#d40000")
Accuracy50 = lipgloss.Color("#e88b00")
Accuracy70 = lipgloss.Color("#e8e400")
Accuracy80 = lipgloss.Color("#b2c906")
Accuracy90 = lipgloss.Color("#95c906")
Accuracy95 = lipgloss.Color("#71c906")
Accuracy98 = lipgloss.Color("#4ac906")
Accuracy100 = lipgloss.Color("#09ff00")
)
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func main() {
p := tea.NewProgram(app.NewAppModel(app.StateDefault))
p := tea.NewProgram(app.NewAppModel(app.StateDefault, nil))
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
Expand Down

0 comments on commit e00d826

Please sign in to comment.