package achievements import ( "encoding/json" "errors" "io/fs" "os" "path/filepath" ) type Achievement struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Icon string `json:"icon"` Unlocked bool `json:"unlocked"` Progress int `json:"progress"` Target int `json:"target"` } type Manager struct { Achievements map[string]*Achievement `json:"achievements"` NewUnlocks []string `json:"-"` } func New() *Manager { return &Manager{ Achievements: map[string]*Achievement{ "first_win": { ID: "first_win", Name: "First Victory", Description: "Complete your first puzzle", Icon: "🏆", Target: 1, }, "speed_demon": { ID: "speed_demon", Name: "Speed Demon", Description: "Complete an Easy puzzle in under 3 minutes", Icon: "⚡", Target: 1, }, "perfectionist": { ID: "perfectionist", Name: "Perfectionist", Description: "Complete a puzzle without using hints", Icon: "💎", Target: 1, }, "streak_master": { ID: "streak_master", Name: "Streak Master", Description: "Achieve a 5-day streak", Icon: "🔥", Target: 5, }, "century": { ID: "century", Name: "Century Club", Description: "Complete 100 puzzles", Icon: "💯", Target: 100, }, "lunatic_legend": { ID: "lunatic_legend", Name: "Lunatic Legend", Description: "Complete 10 Lunatic puzzles", Icon: "🌙", Target: 10, }, "daily_devotee": { ID: "daily_devotee", Name: "Daily Devotee", Description: "Complete 30 daily puzzles", Icon: "📅", Target: 30, }, "no_mistakes": { ID: "no_mistakes", Name: "Flawless", Description: "Complete a Hard puzzle without auto-check", Icon: "✨", Target: 1, }, }, NewUnlocks: []string{}, } } func path() (string, error) { h, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(h, ".termdoku", "achievements.json"), nil } func Load() (*Manager, error) { m := New() p, err := path() if err != nil { return m, err } b, err := os.ReadFile(p) if err != nil { if errors.Is(err, fs.ErrNotExist) { return m, nil } return m, err } if err := json.Unmarshal(b, m); err != nil { return m, err } return m, nil } func Save(m *Manager) error { p, err := path() if err != nil { return err } if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { return err } data, err := json.MarshalIndent(m, "", " ") if err != nil { return err } return os.WriteFile(p, data, 0o644) } func (m *Manager) CheckAndUnlock(id string, progress int) bool { if ach, ok := m.Achievements[id]; ok { if !ach.Unlocked { ach.Progress = progress if ach.Progress >= ach.Target { ach.Unlocked = true m.NewUnlocks = append(m.NewUnlocks, id) return true } } } return false } func (m *Manager) GetUnlockedCount() int { count := 0 for _, ach := range m.Achievements { if ach.Unlocked { count++ } } return count } func (m *Manager) GetTotalCount() int { return len(m.Achievements) } func (m *Manager) ClearNewUnlocks() { m.NewUnlocks = []string{} }