Translate all user-facing strings to Korean across 25 files: - UI screens: title, nickname, lobby, class select, waiting, game, shop, result, help, leaderboard, achievements, codex, stats - Game logic: combat logs, events, achievements, mutations, emotes, lobby errors, session messages - Keep English for: class names, monster names, item names, relic names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/tolelom/catacombs/store"
|
|
)
|
|
|
|
// LeaderboardScreen shows the top runs.
|
|
type LeaderboardScreen struct {
|
|
tab int // 0=all-time, 1=gold, 2=daily
|
|
}
|
|
|
|
func NewLeaderboardScreen() *LeaderboardScreen {
|
|
return &LeaderboardScreen{}
|
|
}
|
|
|
|
func (s *LeaderboardScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
|
|
if key, ok := msg.(tea.KeyMsg); ok {
|
|
if isKey(key, "tab") || key.Type == tea.KeyTab {
|
|
s.tab = (s.tab + 1) % 3
|
|
return s, nil
|
|
}
|
|
if isKey(key, "l") || isEnter(key) || isQuit(key) {
|
|
return NewTitleScreen(), nil
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (s *LeaderboardScreen) View(ctx *Context) string {
|
|
var byFloor, byGold []store.RunRecord
|
|
var daily []store.DailyRecord
|
|
if ctx.Store != nil {
|
|
byFloor, _ = ctx.Store.TopRuns(10)
|
|
byGold, _ = ctx.Store.TopRunsByGold(10)
|
|
daily, _ = ctx.Store.GetDailyLeaderboard(time.Now().Format("2006-01-02"), 20)
|
|
}
|
|
return renderLeaderboard(byFloor, byGold, daily, s.tab, ctx.Width, ctx.Height)
|
|
}
|
|
|
|
func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRecord, tab, width, height int) string {
|
|
title := styleHeader.Render("── 리더보드 ──")
|
|
|
|
// Tab header
|
|
tabs := []string{"층수", "골드", "일일"}
|
|
var tabLine string
|
|
for i, t := range tabs {
|
|
if i == tab {
|
|
tabLine += styleHeader.Render(fmt.Sprintf(" [%s] ", t))
|
|
} else {
|
|
tabLine += styleSystem.Render(fmt.Sprintf(" %s ", t))
|
|
}
|
|
}
|
|
|
|
var content string
|
|
|
|
switch tab {
|
|
case 0: // By Floor
|
|
content += styleCoop.Render(" 층수 순위") + "\n"
|
|
for i, r := range byFloor {
|
|
if i >= 10 {
|
|
break
|
|
}
|
|
medal := fmt.Sprintf(" %d.", i+1)
|
|
cls := ""
|
|
if r.Class != "" {
|
|
cls = fmt.Sprintf(" [%s]", r.Class)
|
|
}
|
|
content += fmt.Sprintf(" %s %s%s B%d %s\n",
|
|
medal, stylePlayer.Render(r.Player), styleSystem.Render(cls),
|
|
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
|
}
|
|
case 1: // By Gold
|
|
content += styleCoop.Render(" 골드 순위") + "\n"
|
|
for i, r := range byGold {
|
|
if i >= 10 {
|
|
break
|
|
}
|
|
medal := fmt.Sprintf(" %d.", i+1)
|
|
cls := ""
|
|
if r.Class != "" {
|
|
cls = fmt.Sprintf(" [%s]", r.Class)
|
|
}
|
|
content += fmt.Sprintf(" %s %s%s B%d %s\n",
|
|
medal, stylePlayer.Render(r.Player), styleSystem.Render(cls),
|
|
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
|
|
}
|
|
case 2: // Daily
|
|
content += styleCoop.Render(fmt.Sprintf(" 일일 도전 — %s", time.Now().Format("2006-01-02"))) + "\n"
|
|
if len(daily) == 0 {
|
|
content += " 오늘 일일 도전 기록이 없습니다.\n"
|
|
}
|
|
for i, r := range daily {
|
|
if i >= 20 {
|
|
break
|
|
}
|
|
medal := fmt.Sprintf(" %d.", i+1)
|
|
content += fmt.Sprintf(" %s %s B%d %s\n",
|
|
medal, stylePlayer.Render(r.PlayerName),
|
|
r.FloorReached, styleGold.Render(fmt.Sprintf("%dg", r.GoldEarned)))
|
|
}
|
|
}
|
|
|
|
footer := styleSystem.Render("\n[Tab] 탭 전환 [L] 뒤로")
|
|
|
|
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
|
|
lipgloss.JoinVertical(lipgloss.Center, title, tabLine, "", content, footer))
|
|
}
|