diff --git a/store/db.go b/store/db.go index 14036a7..d65ec3c 100644 --- a/store/db.go +++ b/store/db.go @@ -76,6 +76,39 @@ func (d *DB) SaveRun(player string, floor, score int) error { }) } +type PlayerStats struct { + TotalRuns int + BestFloor int + TotalGold int + TotalKills int + Victories int +} + +func (d *DB) GetStats(player string) (PlayerStats, error) { + var stats PlayerStats + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketRankings) + if b == nil { + return nil + } + return b.ForEach(func(k, v []byte) error { + var r RunRecord + if json.Unmarshal(v, &r) == nil && r.Player == player { + stats.TotalRuns++ + if r.Floor > stats.BestFloor { + stats.BestFloor = r.Floor + } + stats.TotalGold += r.Score + if r.Floor >= 20 { + stats.Victories++ + } + } + return nil + }) + }) + return stats, err +} + func (d *DB) TopRuns(limit int) ([]RunRecord, error) { var runs []RunRecord err := d.db.View(func(tx *bolt.Tx) error { diff --git a/ui/model.go b/ui/model.go index 8f87bf9..62b5461 100644 --- a/ui/model.go +++ b/ui/model.go @@ -21,6 +21,7 @@ const ( screenShop screenResult screenHelp + screenStats ) // StateUpdateMsg is sent by GameSession to update the view @@ -107,6 +108,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.updateResult(msg) case screenHelp: return m.updateHelp(msg) + case screenStats: + return m.updateStats(msg) } return m, nil } @@ -134,6 +137,12 @@ func (m Model) View() string { return renderResult(m.gameState, rankings) case screenHelp: return renderHelp(m.width, m.height) + case screenStats: + var stats store.PlayerStats + if m.store != nil { + stats, _ = m.store.GetStats(m.playerName) + } + return renderStats(m.playerName, stats, m.width, m.height) } return "" } @@ -187,6 +196,8 @@ func (m Model) updateTitle(msg tea.Msg) (tea.Model, tea.Cmd) { m = m.withRefreshedLobby() } else if isKey(key, "h") { m.screen = screenHelp + } else if isKey(key, "s") { + m.screen = screenStats } else if isQuit(key) { return m, tea.Quit } @@ -194,6 +205,15 @@ func (m Model) updateTitle(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } +func (m Model) updateStats(msg tea.Msg) (tea.Model, tea.Cmd) { + if key, ok := msg.(tea.KeyMsg); ok { + if isKey(key, "s") || isEnter(key) || isQuit(key) { + m.screen = screenTitle + } + } + return m, nil +} + func (m Model) updateHelp(msg tea.Msg) (tea.Model, tea.Cmd) { if key, ok := msg.(tea.KeyMsg); ok { if isKey(key, "h") || isEnter(key) || isQuit(key) { diff --git a/ui/stats_view.go b/ui/stats_view.go new file mode 100644 index 0000000..489e32b --- /dev/null +++ b/ui/stats_view.go @@ -0,0 +1,30 @@ +package ui + +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" + "github.com/tolelom/catacombs/store" +) + +func renderStats(playerName string, stats store.PlayerStats, width, height int) string { + title := styleHeader.Render("── Player Statistics ──") + + var content string + content += stylePlayer.Render(fmt.Sprintf(" %s", playerName)) + "\n\n" + content += fmt.Sprintf(" Total Runs: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalRuns))) + content += fmt.Sprintf(" Best Floor: %s\n", styleGold.Render(fmt.Sprintf("B%d", stats.BestFloor))) + content += fmt.Sprintf(" Total Gold: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalGold))) + content += fmt.Sprintf(" Victories: %s\n", styleHeal.Render(fmt.Sprintf("%d", stats.Victories))) + + winRate := 0.0 + if stats.TotalRuns > 0 { + winRate = float64(stats.Victories) / float64(stats.TotalRuns) * 100 + } + content += fmt.Sprintf(" Win Rate: %s\n", styleSystem.Render(fmt.Sprintf("%.1f%%", winRate))) + + footer := styleSystem.Render("[S] Back") + + return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center, + lipgloss.JoinVertical(lipgloss.Center, title, "", content, "", footer)) +} diff --git a/ui/title.go b/ui/title.go index 4a53fa9..953a95c 100644 --- a/ui/title.go +++ b/ui/title.go @@ -44,7 +44,7 @@ func renderTitle(width, height int) string { menu := lipgloss.NewStyle(). Foreground(colorWhite). Bold(true). - Render("[Enter] Start [H] Help [Q] Quit") + Render("[Enter] Start [H] Help [S] Stats [Q] Quit") content := lipgloss.JoinVertical(lipgloss.Center, logo,