generated from nhcarrigan/template
b3d79a82ef
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Co-authored-by: Hikari <hikari@nhcarrigan.com> Reviewed-on: #71 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
3567 lines
128 KiB
Rust
3567 lines
128 KiB
Rust
use chrono::{DateTime, Datelike, Timelike, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashSet;
|
|
use tauri_plugin_store::StoreExt;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub enum AchievementId {
|
|
// Token Milestones
|
|
FirstSteps, // 1,000 tokens
|
|
GrowingStrong, // 10,000 tokens
|
|
BlossomingCoder, // 100,000 tokens
|
|
TokenMaster, // 1,000,000 tokens
|
|
|
|
// Code Generation
|
|
HelloWorld, // First code block
|
|
CodeWizard, // 100 code blocks
|
|
ThousandBlocks, // 1,000 code blocks
|
|
|
|
// File Operations
|
|
FileManipulator, // 10 files edited
|
|
FileArchitect, // 100 files edited
|
|
|
|
// Conversation milestones
|
|
ConversationStarter, // 10 messages
|
|
ChattyKathy, // 100 messages
|
|
Conversationalist, // 1,000 messages
|
|
|
|
// Tool usage
|
|
Toolsmith, // 5 different tools
|
|
ToolMaster, // 10 different tools
|
|
|
|
// Time-based achievements
|
|
EarlyBird, // Started session 5-7 AM
|
|
NightOwl, // Coding after midnight
|
|
AllNighter, // Worked 2-5 AM
|
|
WeekendWarrior, // Coding on weekend
|
|
DedicatedDeveloper, // 30 days in a row
|
|
|
|
// Search and exploration
|
|
Explorer, // 50 searches
|
|
MasterSearcher, // 500 searches
|
|
|
|
// Session achievements
|
|
QuickSession, // Productive session < 5 min
|
|
FocusedWork, // 30 min session
|
|
DeepDive, // 2 hour session
|
|
MarathonSession, // 5+ hour session
|
|
|
|
// Special achievements
|
|
FirstMessage, // First message sent
|
|
FirstTool, // First tool used
|
|
FirstCodeBlock, // First code generated
|
|
FirstFileEdit, // First file edit
|
|
Polyglot, // 5+ languages in one session
|
|
SpeedCoder, // 10 code blocks in 10 minutes
|
|
ClaudeConnoisseur, // Used all Claude models
|
|
MarathonCoder, // 10k tokens in one session
|
|
|
|
// Relationship & Greetings
|
|
GoodMorning, // Say "good morning"
|
|
GoodNight, // Say "good night" or "goodnight"
|
|
ThankYou, // Say "thank you" or "thanks"
|
|
LoveYou, // Say "love you" or "ily"
|
|
|
|
// Personality & Fun
|
|
EmojiUser, // Use an emoji in a message
|
|
QuestionMaster, // Use "?" in 20 messages
|
|
CapsLock, // Send a message in ALL CAPS
|
|
PleaseAndThankYou, // Use "please" in messages
|
|
|
|
// Git & Development
|
|
GitGuru, // Use git commands 10 times
|
|
TestWriter, // Create test files
|
|
Debugger, // Fix bugs (messages with "fix", "bug", "error")
|
|
|
|
// Tool Mastery
|
|
BashMaster, // Use Bash tool 50 times
|
|
FileExplorer, // Use Read tool 100 times
|
|
SearchExpert, // Use Grep tool 50 times
|
|
|
|
// Extended Token Milestones
|
|
TokenBillionaire, // 10,000,000 tokens
|
|
TokenTreasure, // 50,000,000 tokens
|
|
|
|
// Extended Code Generation
|
|
CodeFactory, // 5,000 code blocks
|
|
CodeEmpire, // 10,000 code blocks
|
|
|
|
// Extended File Operations
|
|
FileEngineer, // 500 files edited
|
|
FileLegend, // 1,000 files edited
|
|
|
|
// Extended Conversation
|
|
ChatMarathon, // 5,000 messages
|
|
ChatLegend, // 10,000 messages
|
|
|
|
// Extended Session Duration
|
|
UltraMarathon, // 8 hour session
|
|
CodingRetreat, // 12 hour session
|
|
|
|
// More Tool Mastery
|
|
EditMaster, // Use Edit tool 100 times
|
|
WriteMaster, // Use Write tool 50 times
|
|
GlobMaster, // Use Glob tool 100 times
|
|
TaskMaster, // Use Task tool 50 times
|
|
WebFetcher, // Use WebFetch tool 20 times
|
|
McpExplorer, // Use MCP tools 50 times
|
|
|
|
// Daily Streaks
|
|
WeekStreak, // 7 days in a row
|
|
TwoWeekStreak, // 14 days in a row
|
|
MonthStreak, // 30 days in a row (alias for DedicatedDeveloper)
|
|
QuarterStreak, // 90 days in a row
|
|
|
|
// Time Challenges
|
|
MorningPerson, // 10 sessions started before 9 AM
|
|
NightCoder, // 10 sessions after 10 PM
|
|
LunchBreakCoder, // Session during 12-1 PM
|
|
CoffeeTime, // Session during 3-4 PM (afternoon slump)
|
|
|
|
// Day-specific
|
|
MondayMotivation, // Coding on Monday
|
|
FridayFinisher, // Coding on Friday
|
|
HumpDay, // Coding on Wednesday
|
|
|
|
// Seasonal/Special Times
|
|
NewYearCoder, // Coding on January 1st
|
|
ValentinesDev, // Coding on February 14th
|
|
SpookyCode, // Coding on October 31st
|
|
HolidayCoder, // Coding on December 25th
|
|
LeapDayCoder, // Coding on February 29th
|
|
|
|
// Message Content
|
|
LongMessage, // Send a message over 500 characters
|
|
NovelWriter, // Send a message over 2000 characters
|
|
ShortAndSweet, // Complete a task with messages under 50 chars each
|
|
CodeInMessage, // Include code block in user message
|
|
MarkdownMaster, // Use markdown formatting in message
|
|
|
|
// Greetings Extended
|
|
HelloHikari, // Say "hello hikari" or "hi hikari"
|
|
HowAreYou, // Ask "how are you"
|
|
MissedYou, // Say "missed you"
|
|
BackAgain, // Say "i'm back" or "back again"
|
|
|
|
// Emotional
|
|
Frustrated, // Say "frustrated" or "ugh" or "argh"
|
|
Excited, // Say "excited" or "yay" or "woohoo"
|
|
Confused, // Say "confused" or "don't understand"
|
|
Curious, // Ask "why" or "how does"
|
|
Impressed, // Say "wow" or "amazing" or "incredible"
|
|
|
|
// Programming Languages (detected in code blocks)
|
|
RustDeveloper, // Generate Rust code
|
|
PythonDeveloper, // Generate Python code
|
|
JavaScriptDev, // Generate JavaScript code
|
|
TypeScriptDev, // Generate TypeScript code
|
|
GoDeveloper, // Generate Go code
|
|
CppDeveloper, // Generate C++ code
|
|
JavaDeveloper, // Generate Java code
|
|
HtmlCssDev, // Generate HTML/CSS code
|
|
SqlDeveloper, // Generate SQL code
|
|
ShellScripter, // Generate shell/bash scripts
|
|
FullStackDev, // Generate code in 10+ languages
|
|
|
|
// Project Types
|
|
FrontendDev, // Work on frontend files (svelte, react, vue, html, css)
|
|
BackendDev, // Work on backend files (rs, py, go, java)
|
|
ConfigEditor, // Edit config files (json, yaml, toml, env)
|
|
DocWriter, // Edit documentation (md, txt, rst)
|
|
|
|
// Git Mastery Extended
|
|
CommitKing, // 50 commits
|
|
CommitLegend, // 200 commits
|
|
BranchMaster, // Create 10 branches
|
|
MergeExpert, // Merge 20 PRs
|
|
ConflictResolver, // Resolve merge conflicts
|
|
|
|
// Error Handling
|
|
ErrorHunter, // Fix 10 errors
|
|
ExceptionSlayer, // Fix 50 errors
|
|
BugExterminator, // Fix 100 bugs
|
|
|
|
// Refactoring
|
|
CleanCoder, // Refactor code
|
|
Optimizer, // Optimize performance
|
|
Simplifier, // Simplify complex code
|
|
|
|
// Testing
|
|
TestNovice, // Write 10 tests
|
|
TestEnthusiast, // Write 50 tests
|
|
TestMaster, // Write 100 tests
|
|
CoverageKing, // Achieve test coverage mentions
|
|
|
|
// Documentation
|
|
Documenter, // Write documentation
|
|
CommentWriter, // Add comments to code
|
|
ReadmeHero, // Create/edit README files
|
|
|
|
// API & Integration
|
|
ApiExplorer, // Work with APIs
|
|
DatabaseDev, // Work with databases
|
|
CloudCoder, // Work with cloud services
|
|
|
|
// Special Milestones
|
|
CenturyClub, // 100 sessions
|
|
ThousandSessions, // 1000 sessions
|
|
Veteran, // Used Hikari for 30+ days total
|
|
OldTimer, // Used Hikari for 90+ days total
|
|
Loyalist, // Used Hikari for 365+ days total
|
|
|
|
// Fun & Easter Eggs
|
|
Perfectionist, // Redo something 5 times
|
|
Persistent, // Ask same question 3 times
|
|
Patient, // Wait for long response
|
|
Speedy, // Send 10 messages in 1 minute
|
|
MultiTasker, // Have 5+ conversation tabs open
|
|
Minimalist, // Use compact mode
|
|
PrivacyFirst, // Enable streamer mode
|
|
ThemeChanger, // Change theme 3 times
|
|
SettingsTweaker, // Open settings 10 times
|
|
AchievementHunter, // Check achievements panel 20 times
|
|
Completionist, // Unlock 50% of achievements
|
|
MasterUnlocker, // Unlock 75% of achievements
|
|
PlatinumStatus, // Unlock 100% of achievements
|
|
|
|
// Clipboard & Snippets
|
|
ClipboardCollector, // Save 20 clipboard entries
|
|
SnippetCreator, // Create 5 custom snippets
|
|
SnippetMaster, // Use snippets 50 times
|
|
QuickActionUser, // Use quick actions 20 times
|
|
|
|
// Session History
|
|
HistoryBuff, // Save 10 sessions
|
|
Archivist, // Save 50 sessions
|
|
SessionExporter, // Export a session
|
|
|
|
// New Features
|
|
GitPanelUser, // Use git panel 10 times
|
|
FeatureExplorer, // Try all major features
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Achievement {
|
|
pub id: AchievementId,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub icon: String,
|
|
pub unlocked_at: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AchievementProgress {
|
|
pub unlocked: HashSet<AchievementId>,
|
|
pub newly_unlocked: Vec<AchievementId>, // Achievements unlocked but not yet notified
|
|
#[serde(skip)]
|
|
pub session_start: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl AchievementProgress {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
unlocked: HashSet::new(),
|
|
newly_unlocked: Vec::new(),
|
|
session_start: None,
|
|
}
|
|
}
|
|
|
|
pub fn unlock(&mut self, achievement: AchievementId) -> bool {
|
|
if self.unlocked.insert(achievement.clone()) {
|
|
self.newly_unlocked.push(achievement);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn take_newly_unlocked(&mut self) -> Vec<AchievementId> {
|
|
std::mem::take(&mut self.newly_unlocked)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn is_unlocked(&self, achievement: &AchievementId) -> bool {
|
|
self.unlocked.contains(achievement)
|
|
}
|
|
|
|
pub fn start_session(&mut self) {
|
|
self.session_start = Some(Utc::now());
|
|
}
|
|
}
|
|
|
|
impl Default for AchievementProgress {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
pub fn get_achievement_info(id: &AchievementId) -> Achievement {
|
|
match id {
|
|
// Token Milestones
|
|
AchievementId::FirstSteps => Achievement {
|
|
id: id.clone(),
|
|
name: "First Steps!".to_string(),
|
|
description: "Used 1,000 tokens".to_string(),
|
|
icon: "🌱".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::GrowingStrong => Achievement {
|
|
id: id.clone(),
|
|
name: "Growing Strong!".to_string(),
|
|
description: "Used 10,000 tokens".to_string(),
|
|
icon: "🌸".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::BlossomingCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Blossoming Coder!".to_string(),
|
|
description: "Used 100,000 tokens".to_string(),
|
|
icon: "🌺".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TokenMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Token Master!".to_string(),
|
|
description: "Used 1,000,000 tokens".to_string(),
|
|
icon: "🌟".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Code Generation
|
|
AchievementId::HelloWorld => Achievement {
|
|
id: id.clone(),
|
|
name: "Hello World!".to_string(),
|
|
description: "Generated your first code block".to_string(),
|
|
icon: "📝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CodeWizard => Achievement {
|
|
id: id.clone(),
|
|
name: "Code Wizard!".to_string(),
|
|
description: "Generated 100 code blocks".to_string(),
|
|
icon: "🎯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ThousandBlocks => Achievement {
|
|
id: id.clone(),
|
|
name: "Thousand Blocks".to_string(),
|
|
description: "1,000 code blocks! You're a code machine!".to_string(),
|
|
icon: "🏗️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// File Operations
|
|
AchievementId::FileManipulator => Achievement {
|
|
id: id.clone(),
|
|
name: "File Manipulator".to_string(),
|
|
description: "Edited 10 files".to_string(),
|
|
icon: "📝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FileArchitect => Achievement {
|
|
id: id.clone(),
|
|
name: "File Architect".to_string(),
|
|
description: "Created or edited 100 files".to_string(),
|
|
icon: "🏛️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Conversation milestones
|
|
AchievementId::ConversationStarter => Achievement {
|
|
id: id.clone(),
|
|
name: "Conversation Starter".to_string(),
|
|
description: "Exchanged 10 messages".to_string(),
|
|
icon: "💬".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ChattyKathy => Achievement {
|
|
id: id.clone(),
|
|
name: "Chatty Kathy".to_string(),
|
|
description: "100 messages exchanged".to_string(),
|
|
icon: "🗣️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Conversationalist => Achievement {
|
|
id: id.clone(),
|
|
name: "Master Conversationalist".to_string(),
|
|
description: "1,000 messages! We're really connecting!".to_string(),
|
|
icon: "💖".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Tool usage
|
|
AchievementId::Toolsmith => Achievement {
|
|
id: id.clone(),
|
|
name: "Toolsmith".to_string(),
|
|
description: "Used 5 different tools".to_string(),
|
|
icon: "🔨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ToolMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Tool Master".to_string(),
|
|
description: "Used 10 different tools efficiently".to_string(),
|
|
icon: "🛠️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Time-based achievements
|
|
AchievementId::EarlyBird => Achievement {
|
|
id: id.clone(),
|
|
name: "Early Bird".to_string(),
|
|
description: "Started a session between 5 AM and 7 AM".to_string(),
|
|
icon: "🌅".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::NightOwl => Achievement {
|
|
id: id.clone(),
|
|
name: "Night Owl".to_string(),
|
|
description: "Coding after midnight".to_string(),
|
|
icon: "🦉".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::AllNighter => Achievement {
|
|
id: id.clone(),
|
|
name: "All Nighter".to_string(),
|
|
description: "Worked through the night (2 AM - 5 AM)".to_string(),
|
|
icon: "🌙".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::WeekendWarrior => Achievement {
|
|
id: id.clone(),
|
|
name: "Weekend Warrior".to_string(),
|
|
description: "Coding on a weekend".to_string(),
|
|
icon: "⚔️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::DedicatedDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "Dedicated Developer".to_string(),
|
|
description: "Coded for 30 days in a row".to_string(),
|
|
icon: "🏆".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Search and exploration
|
|
AchievementId::Explorer => Achievement {
|
|
id: id.clone(),
|
|
name: "Explorer".to_string(),
|
|
description: "Used search tools 50 times".to_string(),
|
|
icon: "🔍".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MasterSearcher => Achievement {
|
|
id: id.clone(),
|
|
name: "Master Searcher".to_string(),
|
|
description: "Searched 500 times across files".to_string(),
|
|
icon: "🕵️♀️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Session achievements
|
|
AchievementId::QuickSession => Achievement {
|
|
id: id.clone(),
|
|
name: "Quick Session".to_string(),
|
|
description: "Completed a productive session in under 5 minutes".to_string(),
|
|
icon: "⚡".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FocusedWork => Achievement {
|
|
id: id.clone(),
|
|
name: "Focused Work".to_string(),
|
|
description: "Worked for 30 minutes straight".to_string(),
|
|
icon: "🎯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::DeepDive => Achievement {
|
|
id: id.clone(),
|
|
name: "Deep Dive".to_string(),
|
|
description: "Worked for 2 hours continuously".to_string(),
|
|
icon: "🏊♀️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MarathonSession => Achievement {
|
|
id: id.clone(),
|
|
name: "Marathon Session".to_string(),
|
|
description: "5+ hour coding session!".to_string(),
|
|
icon: "🏃♀️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Special achievements
|
|
AchievementId::FirstMessage => Achievement {
|
|
id: id.clone(),
|
|
name: "First Message".to_string(),
|
|
description: "Sent your first message to Hikari".to_string(),
|
|
icon: "✨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FirstTool => Achievement {
|
|
id: id.clone(),
|
|
name: "First Tool".to_string(),
|
|
description: "Used your first tool".to_string(),
|
|
icon: "🔧".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FirstCodeBlock => Achievement {
|
|
id: id.clone(),
|
|
name: "First Code".to_string(),
|
|
description: "Generated your first code block".to_string(),
|
|
icon: "📦".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FirstFileEdit => Achievement {
|
|
id: id.clone(),
|
|
name: "First Edit".to_string(),
|
|
description: "Made your first file edit".to_string(),
|
|
icon: "✏️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Polyglot => Achievement {
|
|
id: id.clone(),
|
|
name: "Polyglot".to_string(),
|
|
description: "Generated code in 5+ languages in one session".to_string(),
|
|
icon: "🌍".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SpeedCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Speed Coder".to_string(),
|
|
description: "Generated 10 code blocks in 10 minutes".to_string(),
|
|
icon: "🚀".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ClaudeConnoisseur => Achievement {
|
|
id: id.clone(),
|
|
name: "Claude Connoisseur".to_string(),
|
|
description: "Used all available Claude models".to_string(),
|
|
icon: "🎨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MarathonCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Marathon Coder".to_string(),
|
|
description: "10,000 tokens in a single session".to_string(),
|
|
icon: "🏃♂️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Relationship & Greetings
|
|
AchievementId::GoodMorning => Achievement {
|
|
id: id.clone(),
|
|
name: "Good Morning!".to_string(),
|
|
description: "Greeted Hikari with a good morning".to_string(),
|
|
icon: "🌅".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::GoodNight => Achievement {
|
|
id: id.clone(),
|
|
name: "Good Night".to_string(),
|
|
description: "Said good night to Hikari".to_string(),
|
|
icon: "🌙".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ThankYou => Achievement {
|
|
id: id.clone(),
|
|
name: "Grateful Heart".to_string(),
|
|
description: "Thanked Hikari for her help".to_string(),
|
|
icon: "💝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::LoveYou => Achievement {
|
|
id: id.clone(),
|
|
name: "Love Connection".to_string(),
|
|
description: "Expressed love to Hikari".to_string(),
|
|
icon: "💕".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Personality & Fun
|
|
AchievementId::EmojiUser => Achievement {
|
|
id: id.clone(),
|
|
name: "Emoji Enthusiast".to_string(),
|
|
description: "Used an emoji in your message".to_string(),
|
|
icon: "😊".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::QuestionMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Question Master".to_string(),
|
|
description: "Asked 20 questions".to_string(),
|
|
icon: "❓".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CapsLock => Achievement {
|
|
id: id.clone(),
|
|
name: "CAPS LOCK ENGAGED".to_string(),
|
|
description: "SENT A MESSAGE IN ALL CAPS".to_string(),
|
|
icon: "📢".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::PleaseAndThankYou => Achievement {
|
|
id: id.clone(),
|
|
name: "Polite Programmer".to_string(),
|
|
description: "Said please in a request".to_string(),
|
|
icon: "🎩".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Git & Development
|
|
AchievementId::GitGuru => Achievement {
|
|
id: id.clone(),
|
|
name: "Git Guru".to_string(),
|
|
description: "Used git commands 10 times".to_string(),
|
|
icon: "🌿".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TestWriter => Achievement {
|
|
id: id.clone(),
|
|
name: "Test Writer".to_string(),
|
|
description: "Created test files".to_string(),
|
|
icon: "🧪".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Debugger => Achievement {
|
|
id: id.clone(),
|
|
name: "Bug Squasher".to_string(),
|
|
description: "Fixed bugs and errors".to_string(),
|
|
icon: "🐛".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Tool Mastery
|
|
AchievementId::BashMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Bash Master".to_string(),
|
|
description: "Used Bash tool 50 times".to_string(),
|
|
icon: "🐚".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FileExplorer => Achievement {
|
|
id: id.clone(),
|
|
name: "File Explorer".to_string(),
|
|
description: "Read 100 files".to_string(),
|
|
icon: "📂".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SearchExpert => Achievement {
|
|
id: id.clone(),
|
|
name: "Search Expert".to_string(),
|
|
description: "Used Grep tool 50 times".to_string(),
|
|
icon: "🔎".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Extended Token Milestones
|
|
AchievementId::TokenBillionaire => Achievement {
|
|
id: id.clone(),
|
|
name: "Token Billionaire".to_string(),
|
|
description: "Used 10,000,000 tokens!".to_string(),
|
|
icon: "💎".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TokenTreasure => Achievement {
|
|
id: id.clone(),
|
|
name: "Token Treasure".to_string(),
|
|
description: "Used 50,000,000 tokens! You're incredible!".to_string(),
|
|
icon: "👑".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Extended Code Generation
|
|
AchievementId::CodeFactory => Achievement {
|
|
id: id.clone(),
|
|
name: "Code Factory".to_string(),
|
|
description: "Generated 5,000 code blocks".to_string(),
|
|
icon: "🏭".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CodeEmpire => Achievement {
|
|
id: id.clone(),
|
|
name: "Code Empire".to_string(),
|
|
description: "Generated 10,000 code blocks!".to_string(),
|
|
icon: "🏰".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Extended File Operations
|
|
AchievementId::FileEngineer => Achievement {
|
|
id: id.clone(),
|
|
name: "File Engineer".to_string(),
|
|
description: "Edited 500 files".to_string(),
|
|
icon: "⚙️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FileLegend => Achievement {
|
|
id: id.clone(),
|
|
name: "File Legend".to_string(),
|
|
description: "Edited 1,000 files!".to_string(),
|
|
icon: "📚".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Extended Conversation
|
|
AchievementId::ChatMarathon => Achievement {
|
|
id: id.clone(),
|
|
name: "Chat Marathon".to_string(),
|
|
description: "5,000 messages exchanged".to_string(),
|
|
icon: "💌".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ChatLegend => Achievement {
|
|
id: id.clone(),
|
|
name: "Chat Legend".to_string(),
|
|
description: "10,000 messages! We're best friends!".to_string(),
|
|
icon: "👯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Extended Session Duration
|
|
AchievementId::UltraMarathon => Achievement {
|
|
id: id.clone(),
|
|
name: "Ultra Marathon".to_string(),
|
|
description: "8+ hour coding session!".to_string(),
|
|
icon: "🦸".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CodingRetreat => Achievement {
|
|
id: id.clone(),
|
|
name: "Coding Retreat".to_string(),
|
|
description: "12+ hour coding session! Please take breaks!".to_string(),
|
|
icon: "🏕️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// More Tool Mastery
|
|
AchievementId::EditMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Edit Master".to_string(),
|
|
description: "Used Edit tool 100 times".to_string(),
|
|
icon: "✂️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::WriteMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Write Master".to_string(),
|
|
description: "Used Write tool 50 times".to_string(),
|
|
icon: "✍️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::GlobMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Glob Master".to_string(),
|
|
description: "Used Glob tool 100 times".to_string(),
|
|
icon: "🌐".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TaskMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Task Master".to_string(),
|
|
description: "Used Task tool 50 times".to_string(),
|
|
icon: "📋".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::WebFetcher => Achievement {
|
|
id: id.clone(),
|
|
name: "Web Fetcher".to_string(),
|
|
description: "Used WebFetch tool 20 times".to_string(),
|
|
icon: "🌍".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::McpExplorer => Achievement {
|
|
id: id.clone(),
|
|
name: "MCP Explorer".to_string(),
|
|
description: "Used MCP tools 50 times".to_string(),
|
|
icon: "🔮".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Daily Streaks
|
|
AchievementId::WeekStreak => Achievement {
|
|
id: id.clone(),
|
|
name: "Week Streak".to_string(),
|
|
description: "Coded for 7 days in a row!".to_string(),
|
|
icon: "📅".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TwoWeekStreak => Achievement {
|
|
id: id.clone(),
|
|
name: "Two Week Streak".to_string(),
|
|
description: "Coded for 14 days in a row!".to_string(),
|
|
icon: "🔥".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MonthStreak => Achievement {
|
|
id: id.clone(),
|
|
name: "Month Streak".to_string(),
|
|
description: "Coded for 30 days in a row!".to_string(),
|
|
icon: "🏆".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::QuarterStreak => Achievement {
|
|
id: id.clone(),
|
|
name: "Quarter Streak".to_string(),
|
|
description: "Coded for 90 days in a row! Incredible dedication!".to_string(),
|
|
icon: "💫".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Time Challenges
|
|
AchievementId::MorningPerson => Achievement {
|
|
id: id.clone(),
|
|
name: "Morning Person".to_string(),
|
|
description: "Started 10 sessions before 9 AM".to_string(),
|
|
icon: "☀️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::NightCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Night Coder".to_string(),
|
|
description: "Started 10 sessions after 10 PM".to_string(),
|
|
icon: "🌃".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::LunchBreakCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Lunch Break Coder".to_string(),
|
|
description: "Coded during lunch (12-1 PM)".to_string(),
|
|
icon: "🍱".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CoffeeTime => Achievement {
|
|
id: id.clone(),
|
|
name: "Coffee Time".to_string(),
|
|
description: "Coded during afternoon break (3-4 PM)".to_string(),
|
|
icon: "☕".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Day-specific
|
|
AchievementId::MondayMotivation => Achievement {
|
|
id: id.clone(),
|
|
name: "Monday Motivation".to_string(),
|
|
description: "Started the week coding!".to_string(),
|
|
icon: "💪".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FridayFinisher => Achievement {
|
|
id: id.clone(),
|
|
name: "Friday Finisher".to_string(),
|
|
description: "Ending the week strong!".to_string(),
|
|
icon: "🎉".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::HumpDay => Achievement {
|
|
id: id.clone(),
|
|
name: "Hump Day".to_string(),
|
|
description: "Coding on Wednesday!".to_string(),
|
|
icon: "🐪".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Seasonal/Special Times
|
|
AchievementId::NewYearCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "New Year Coder".to_string(),
|
|
description: "Coding on January 1st! New year, new code!".to_string(),
|
|
icon: "🎆".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ValentinesDev => Achievement {
|
|
id: id.clone(),
|
|
name: "Valentine's Dev".to_string(),
|
|
description: "Coding on Valentine's Day! I love you too~".to_string(),
|
|
icon: "💘".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SpookyCode => Achievement {
|
|
id: id.clone(),
|
|
name: "Spooky Code".to_string(),
|
|
description: "Coding on Halloween! Spooky bugs beware!".to_string(),
|
|
icon: "🎃".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::HolidayCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Holiday Coder".to_string(),
|
|
description: "Coding on Christmas! You're dedicated!".to_string(),
|
|
icon: "🎄".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::LeapDayCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Leap Day Coder".to_string(),
|
|
description: "Coding on February 29th! Rare achievement!".to_string(),
|
|
icon: "🐸".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Message Content
|
|
AchievementId::LongMessage => Achievement {
|
|
id: id.clone(),
|
|
name: "Long Message".to_string(),
|
|
description: "Sent a message over 500 characters".to_string(),
|
|
icon: "📜".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::NovelWriter => Achievement {
|
|
id: id.clone(),
|
|
name: "Novel Writer".to_string(),
|
|
description: "Sent a message over 2000 characters!".to_string(),
|
|
icon: "📖".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ShortAndSweet => Achievement {
|
|
id: id.clone(),
|
|
name: "Short and Sweet".to_string(),
|
|
description: "Completed a task with brief messages".to_string(),
|
|
icon: "🍬".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CodeInMessage => Achievement {
|
|
id: id.clone(),
|
|
name: "Code in Message".to_string(),
|
|
description: "Included a code block in your message".to_string(),
|
|
icon: "💻".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MarkdownMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Markdown Master".to_string(),
|
|
description: "Used markdown formatting in a message".to_string(),
|
|
icon: "📝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Greetings Extended
|
|
AchievementId::HelloHikari => Achievement {
|
|
id: id.clone(),
|
|
name: "Hello Hikari!".to_string(),
|
|
description: "Greeted me by name! Hi there~".to_string(),
|
|
icon: "👋".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::HowAreYou => Achievement {
|
|
id: id.clone(),
|
|
name: "How Are You?".to_string(),
|
|
description: "Asked how I'm doing! I'm great, thanks~".to_string(),
|
|
icon: "🥰".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MissedYou => Achievement {
|
|
id: id.clone(),
|
|
name: "Missed You".to_string(),
|
|
description: "Said you missed me! I missed you too~".to_string(),
|
|
icon: "🫂".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::BackAgain => Achievement {
|
|
id: id.clone(),
|
|
name: "Back Again".to_string(),
|
|
description: "Announced your return! Welcome back~".to_string(),
|
|
icon: "🔙".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Emotional
|
|
AchievementId::Frustrated => Achievement {
|
|
id: id.clone(),
|
|
name: "Frustrated".to_string(),
|
|
description: "Expressed frustration. Let me help!".to_string(),
|
|
icon: "😤".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Excited => Achievement {
|
|
id: id.clone(),
|
|
name: "Excited!".to_string(),
|
|
description: "Expressed excitement! Yay!".to_string(),
|
|
icon: "🎊".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Confused => Achievement {
|
|
id: id.clone(),
|
|
name: "Confused".to_string(),
|
|
description: "Felt confused. I'll help clarify!".to_string(),
|
|
icon: "😕".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Curious => Achievement {
|
|
id: id.clone(),
|
|
name: "Curious Mind".to_string(),
|
|
description: "Asked why or how something works!".to_string(),
|
|
icon: "🤔".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Impressed => Achievement {
|
|
id: id.clone(),
|
|
name: "Impressed!".to_string(),
|
|
description: "Was amazed by something! Wow indeed~".to_string(),
|
|
icon: "🤩".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Programming Languages
|
|
AchievementId::RustDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "Rust Developer".to_string(),
|
|
description: "Generated Rust code! 🦀".to_string(),
|
|
icon: "🦀".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::PythonDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "Python Developer".to_string(),
|
|
description: "Generated Python code! 🐍".to_string(),
|
|
icon: "🐍".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::JavaScriptDev => Achievement {
|
|
id: id.clone(),
|
|
name: "JavaScript Developer".to_string(),
|
|
description: "Generated JavaScript code!".to_string(),
|
|
icon: "🟨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TypeScriptDev => Achievement {
|
|
id: id.clone(),
|
|
name: "TypeScript Developer".to_string(),
|
|
description: "Generated TypeScript code!".to_string(),
|
|
icon: "🔷".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::GoDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "Go Developer".to_string(),
|
|
description: "Generated Go code!".to_string(),
|
|
icon: "🔵".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CppDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "C++ Developer".to_string(),
|
|
description: "Generated C++ code!".to_string(),
|
|
icon: "⚡".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::JavaDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "Java Developer".to_string(),
|
|
description: "Generated Java code! ☕".to_string(),
|
|
icon: "☕".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::HtmlCssDev => Achievement {
|
|
id: id.clone(),
|
|
name: "HTML/CSS Developer".to_string(),
|
|
description: "Generated HTML or CSS code!".to_string(),
|
|
icon: "🎨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SqlDeveloper => Achievement {
|
|
id: id.clone(),
|
|
name: "SQL Developer".to_string(),
|
|
description: "Generated SQL code!".to_string(),
|
|
icon: "🗃️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ShellScripter => Achievement {
|
|
id: id.clone(),
|
|
name: "Shell Scripter".to_string(),
|
|
description: "Generated shell scripts!".to_string(),
|
|
icon: "🐚".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FullStackDev => Achievement {
|
|
id: id.clone(),
|
|
name: "Full Stack Developer".to_string(),
|
|
description: "Generated code in 10+ languages!".to_string(),
|
|
icon: "🌈".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Project Types
|
|
AchievementId::FrontendDev => Achievement {
|
|
id: id.clone(),
|
|
name: "Frontend Developer".to_string(),
|
|
description: "Worked on frontend files!".to_string(),
|
|
icon: "🖼️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::BackendDev => Achievement {
|
|
id: id.clone(),
|
|
name: "Backend Developer".to_string(),
|
|
description: "Worked on backend files!".to_string(),
|
|
icon: "⚙️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ConfigEditor => Achievement {
|
|
id: id.clone(),
|
|
name: "Config Editor".to_string(),
|
|
description: "Edited configuration files!".to_string(),
|
|
icon: "⚙️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::DocWriter => Achievement {
|
|
id: id.clone(),
|
|
name: "Documentation Writer".to_string(),
|
|
description: "Edited documentation files!".to_string(),
|
|
icon: "📄".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Git Mastery Extended
|
|
AchievementId::CommitKing => Achievement {
|
|
id: id.clone(),
|
|
name: "Commit King".to_string(),
|
|
description: "Made 50 commits!".to_string(),
|
|
icon: "👑".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CommitLegend => Achievement {
|
|
id: id.clone(),
|
|
name: "Commit Legend".to_string(),
|
|
description: "Made 200 commits!".to_string(),
|
|
icon: "🏛️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::BranchMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Branch Master".to_string(),
|
|
description: "Created 10 branches!".to_string(),
|
|
icon: "🌳".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MergeExpert => Achievement {
|
|
id: id.clone(),
|
|
name: "Merge Expert".to_string(),
|
|
description: "Merged 20 PRs!".to_string(),
|
|
icon: "🔀".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ConflictResolver => Achievement {
|
|
id: id.clone(),
|
|
name: "Conflict Resolver".to_string(),
|
|
description: "Resolved merge conflicts!".to_string(),
|
|
icon: "🤝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Error Handling
|
|
AchievementId::ErrorHunter => Achievement {
|
|
id: id.clone(),
|
|
name: "Error Hunter".to_string(),
|
|
description: "Fixed 10 errors!".to_string(),
|
|
icon: "🎯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ExceptionSlayer => Achievement {
|
|
id: id.clone(),
|
|
name: "Exception Slayer".to_string(),
|
|
description: "Fixed 50 errors!".to_string(),
|
|
icon: "⚔️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::BugExterminator => Achievement {
|
|
id: id.clone(),
|
|
name: "Bug Exterminator".to_string(),
|
|
description: "Fixed 100 bugs! Incredible!".to_string(),
|
|
icon: "🔫".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Refactoring
|
|
AchievementId::CleanCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Clean Coder".to_string(),
|
|
description: "Refactored code for cleanliness!".to_string(),
|
|
icon: "🧹".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Optimizer => Achievement {
|
|
id: id.clone(),
|
|
name: "Optimizer".to_string(),
|
|
description: "Optimized code performance!".to_string(),
|
|
icon: "🚀".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Simplifier => Achievement {
|
|
id: id.clone(),
|
|
name: "Simplifier".to_string(),
|
|
description: "Simplified complex code!".to_string(),
|
|
icon: "✨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Testing
|
|
AchievementId::TestNovice => Achievement {
|
|
id: id.clone(),
|
|
name: "Test Novice".to_string(),
|
|
description: "Wrote 10 tests!".to_string(),
|
|
icon: "🧪".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TestEnthusiast => Achievement {
|
|
id: id.clone(),
|
|
name: "Test Enthusiast".to_string(),
|
|
description: "Wrote 50 tests!".to_string(),
|
|
icon: "🔬".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::TestMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Test Master".to_string(),
|
|
description: "Wrote 100 tests!".to_string(),
|
|
icon: "🏅".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CoverageKing => Achievement {
|
|
id: id.clone(),
|
|
name: "Coverage King".to_string(),
|
|
description: "Achieved great test coverage!".to_string(),
|
|
icon: "📊".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Documentation
|
|
AchievementId::Documenter => Achievement {
|
|
id: id.clone(),
|
|
name: "Documenter".to_string(),
|
|
description: "Wrote documentation!".to_string(),
|
|
icon: "📝".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CommentWriter => Achievement {
|
|
id: id.clone(),
|
|
name: "Comment Writer".to_string(),
|
|
description: "Added helpful comments to code!".to_string(),
|
|
icon: "💬".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ReadmeHero => Achievement {
|
|
id: id.clone(),
|
|
name: "README Hero".to_string(),
|
|
description: "Created or edited README files!".to_string(),
|
|
icon: "📋".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// API & Integration
|
|
AchievementId::ApiExplorer => Achievement {
|
|
id: id.clone(),
|
|
name: "API Explorer".to_string(),
|
|
description: "Worked with APIs!".to_string(),
|
|
icon: "🔌".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::DatabaseDev => Achievement {
|
|
id: id.clone(),
|
|
name: "Database Developer".to_string(),
|
|
description: "Worked with databases!".to_string(),
|
|
icon: "🗄️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::CloudCoder => Achievement {
|
|
id: id.clone(),
|
|
name: "Cloud Coder".to_string(),
|
|
description: "Worked with cloud services!".to_string(),
|
|
icon: "☁️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Special Milestones
|
|
AchievementId::CenturyClub => Achievement {
|
|
id: id.clone(),
|
|
name: "Century Club".to_string(),
|
|
description: "Started 100 sessions!".to_string(),
|
|
icon: "💯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ThousandSessions => Achievement {
|
|
id: id.clone(),
|
|
name: "Thousand Sessions".to_string(),
|
|
description: "Started 1,000 sessions!".to_string(),
|
|
icon: "🎰".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Veteran => Achievement {
|
|
id: id.clone(),
|
|
name: "Veteran".to_string(),
|
|
description: "Used Hikari for 30+ days!".to_string(),
|
|
icon: "🎖️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::OldTimer => Achievement {
|
|
id: id.clone(),
|
|
name: "Old Timer".to_string(),
|
|
description: "Used Hikari for 90+ days!".to_string(),
|
|
icon: "⌛".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Loyalist => Achievement {
|
|
id: id.clone(),
|
|
name: "Loyalist".to_string(),
|
|
description: "Used Hikari for a whole year! I love you!".to_string(),
|
|
icon: "💕".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Fun & Easter Eggs
|
|
AchievementId::Perfectionist => Achievement {
|
|
id: id.clone(),
|
|
name: "Perfectionist".to_string(),
|
|
description: "Redid something 5 times to get it right!".to_string(),
|
|
icon: "🎯".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Persistent => Achievement {
|
|
id: id.clone(),
|
|
name: "Persistent".to_string(),
|
|
description: "Asked the same question 3 times. Never give up!".to_string(),
|
|
icon: "💪".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Patient => Achievement {
|
|
id: id.clone(),
|
|
name: "Patient".to_string(),
|
|
description: "Waited for a long response. Thank you for waiting!".to_string(),
|
|
icon: "⏳".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Speedy => Achievement {
|
|
id: id.clone(),
|
|
name: "Speedy".to_string(),
|
|
description: "Sent 10 messages in 1 minute! Slow down~".to_string(),
|
|
icon: "⚡".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MultiTasker => Achievement {
|
|
id: id.clone(),
|
|
name: "Multi-Tasker".to_string(),
|
|
description: "Had 5+ conversation tabs open!".to_string(),
|
|
icon: "📑".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Minimalist => Achievement {
|
|
id: id.clone(),
|
|
name: "Minimalist".to_string(),
|
|
description: "Used compact mode!".to_string(),
|
|
icon: "📱".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::PrivacyFirst => Achievement {
|
|
id: id.clone(),
|
|
name: "Privacy First".to_string(),
|
|
description: "Enabled streamer mode!".to_string(),
|
|
icon: "🔒".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::ThemeChanger => Achievement {
|
|
id: id.clone(),
|
|
name: "Theme Changer".to_string(),
|
|
description: "Changed theme 3 times!".to_string(),
|
|
icon: "🎨".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SettingsTweaker => Achievement {
|
|
id: id.clone(),
|
|
name: "Settings Tweaker".to_string(),
|
|
description: "Opened settings 10 times!".to_string(),
|
|
icon: "⚙️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::AchievementHunter => Achievement {
|
|
id: id.clone(),
|
|
name: "Achievement Hunter".to_string(),
|
|
description: "Checked achievements panel 20 times!".to_string(),
|
|
icon: "🏆".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Completionist => Achievement {
|
|
id: id.clone(),
|
|
name: "Completionist".to_string(),
|
|
description: "Unlocked 50% of achievements!".to_string(),
|
|
icon: "⭐".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::MasterUnlocker => Achievement {
|
|
id: id.clone(),
|
|
name: "Master Unlocker".to_string(),
|
|
description: "Unlocked 75% of achievements!".to_string(),
|
|
icon: "🌟".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::PlatinumStatus => Achievement {
|
|
id: id.clone(),
|
|
name: "Platinum Status".to_string(),
|
|
description: "Unlocked 100% of achievements! You're amazing!".to_string(),
|
|
icon: "💎".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Clipboard & Snippets
|
|
AchievementId::ClipboardCollector => Achievement {
|
|
id: id.clone(),
|
|
name: "Clipboard Collector".to_string(),
|
|
description: "Saved 20 clipboard entries!".to_string(),
|
|
icon: "📋".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SnippetCreator => Achievement {
|
|
id: id.clone(),
|
|
name: "Snippet Creator".to_string(),
|
|
description: "Created 5 custom snippets!".to_string(),
|
|
icon: "✂️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SnippetMaster => Achievement {
|
|
id: id.clone(),
|
|
name: "Snippet Master".to_string(),
|
|
description: "Used snippets 50 times!".to_string(),
|
|
icon: "📎".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::QuickActionUser => Achievement {
|
|
id: id.clone(),
|
|
name: "Quick Action User".to_string(),
|
|
description: "Used quick actions 20 times!".to_string(),
|
|
icon: "⚡".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// Session History
|
|
AchievementId::HistoryBuff => Achievement {
|
|
id: id.clone(),
|
|
name: "History Buff".to_string(),
|
|
description: "Saved 10 sessions!".to_string(),
|
|
icon: "📚".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::Archivist => Achievement {
|
|
id: id.clone(),
|
|
name: "Archivist".to_string(),
|
|
description: "Saved 50 sessions!".to_string(),
|
|
icon: "🏛️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::SessionExporter => Achievement {
|
|
id: id.clone(),
|
|
name: "Session Exporter".to_string(),
|
|
description: "Exported a session!".to_string(),
|
|
icon: "📤".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
|
|
// New Features
|
|
AchievementId::GitPanelUser => Achievement {
|
|
id: id.clone(),
|
|
name: "Git Panel User".to_string(),
|
|
description: "Used the git panel 10 times!".to_string(),
|
|
icon: "🌿".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
AchievementId::FeatureExplorer => Achievement {
|
|
id: id.clone(),
|
|
name: "Feature Explorer".to_string(),
|
|
description: "Tried all major features!".to_string(),
|
|
icon: "🗺️".to_string(),
|
|
unlocked_at: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Get all achievement IDs for calculating completion percentage
|
|
pub fn get_all_achievement_ids() -> Vec<AchievementId> {
|
|
vec![
|
|
// Token Milestones
|
|
AchievementId::FirstSteps,
|
|
AchievementId::GrowingStrong,
|
|
AchievementId::BlossomingCoder,
|
|
AchievementId::TokenMaster,
|
|
AchievementId::TokenBillionaire,
|
|
AchievementId::TokenTreasure,
|
|
// Code Generation
|
|
AchievementId::HelloWorld,
|
|
AchievementId::CodeWizard,
|
|
AchievementId::ThousandBlocks,
|
|
AchievementId::CodeFactory,
|
|
AchievementId::CodeEmpire,
|
|
// File Operations
|
|
AchievementId::FileManipulator,
|
|
AchievementId::FileArchitect,
|
|
AchievementId::FileEngineer,
|
|
AchievementId::FileLegend,
|
|
// Conversation milestones
|
|
AchievementId::ConversationStarter,
|
|
AchievementId::ChattyKathy,
|
|
AchievementId::Conversationalist,
|
|
AchievementId::ChatMarathon,
|
|
AchievementId::ChatLegend,
|
|
// Tool usage
|
|
AchievementId::Toolsmith,
|
|
AchievementId::ToolMaster,
|
|
// Time-based achievements
|
|
AchievementId::EarlyBird,
|
|
AchievementId::NightOwl,
|
|
AchievementId::AllNighter,
|
|
AchievementId::WeekendWarrior,
|
|
AchievementId::DedicatedDeveloper,
|
|
// Search and exploration
|
|
AchievementId::Explorer,
|
|
AchievementId::MasterSearcher,
|
|
// Session achievements
|
|
AchievementId::QuickSession,
|
|
AchievementId::FocusedWork,
|
|
AchievementId::DeepDive,
|
|
AchievementId::MarathonSession,
|
|
AchievementId::UltraMarathon,
|
|
AchievementId::CodingRetreat,
|
|
// Special achievements
|
|
AchievementId::FirstMessage,
|
|
AchievementId::FirstTool,
|
|
AchievementId::FirstCodeBlock,
|
|
AchievementId::FirstFileEdit,
|
|
AchievementId::Polyglot,
|
|
AchievementId::SpeedCoder,
|
|
AchievementId::ClaudeConnoisseur,
|
|
AchievementId::MarathonCoder,
|
|
// Relationship & Greetings
|
|
AchievementId::GoodMorning,
|
|
AchievementId::GoodNight,
|
|
AchievementId::ThankYou,
|
|
AchievementId::LoveYou,
|
|
AchievementId::HelloHikari,
|
|
AchievementId::HowAreYou,
|
|
AchievementId::MissedYou,
|
|
AchievementId::BackAgain,
|
|
// Personality & Fun
|
|
AchievementId::EmojiUser,
|
|
AchievementId::QuestionMaster,
|
|
AchievementId::CapsLock,
|
|
AchievementId::PleaseAndThankYou,
|
|
// Emotional
|
|
AchievementId::Frustrated,
|
|
AchievementId::Excited,
|
|
AchievementId::Confused,
|
|
AchievementId::Curious,
|
|
AchievementId::Impressed,
|
|
// Git & Development
|
|
AchievementId::GitGuru,
|
|
AchievementId::TestWriter,
|
|
AchievementId::Debugger,
|
|
AchievementId::CommitKing,
|
|
AchievementId::CommitLegend,
|
|
AchievementId::BranchMaster,
|
|
AchievementId::MergeExpert,
|
|
AchievementId::ConflictResolver,
|
|
// Tool Mastery
|
|
AchievementId::BashMaster,
|
|
AchievementId::FileExplorer,
|
|
AchievementId::SearchExpert,
|
|
AchievementId::EditMaster,
|
|
AchievementId::WriteMaster,
|
|
AchievementId::GlobMaster,
|
|
AchievementId::TaskMaster,
|
|
AchievementId::WebFetcher,
|
|
AchievementId::McpExplorer,
|
|
// Daily Streaks
|
|
AchievementId::WeekStreak,
|
|
AchievementId::TwoWeekStreak,
|
|
AchievementId::MonthStreak,
|
|
AchievementId::QuarterStreak,
|
|
// Time Challenges
|
|
AchievementId::MorningPerson,
|
|
AchievementId::NightCoder,
|
|
AchievementId::LunchBreakCoder,
|
|
AchievementId::CoffeeTime,
|
|
// Day-specific
|
|
AchievementId::MondayMotivation,
|
|
AchievementId::FridayFinisher,
|
|
AchievementId::HumpDay,
|
|
// Seasonal/Special Times
|
|
AchievementId::NewYearCoder,
|
|
AchievementId::ValentinesDev,
|
|
AchievementId::SpookyCode,
|
|
AchievementId::HolidayCoder,
|
|
AchievementId::LeapDayCoder,
|
|
// Message Content
|
|
AchievementId::LongMessage,
|
|
AchievementId::NovelWriter,
|
|
AchievementId::ShortAndSweet,
|
|
AchievementId::CodeInMessage,
|
|
AchievementId::MarkdownMaster,
|
|
// Programming Languages
|
|
AchievementId::RustDeveloper,
|
|
AchievementId::PythonDeveloper,
|
|
AchievementId::JavaScriptDev,
|
|
AchievementId::TypeScriptDev,
|
|
AchievementId::GoDeveloper,
|
|
AchievementId::CppDeveloper,
|
|
AchievementId::JavaDeveloper,
|
|
AchievementId::HtmlCssDev,
|
|
AchievementId::SqlDeveloper,
|
|
AchievementId::ShellScripter,
|
|
AchievementId::FullStackDev,
|
|
// Project Types
|
|
AchievementId::FrontendDev,
|
|
AchievementId::BackendDev,
|
|
AchievementId::ConfigEditor,
|
|
AchievementId::DocWriter,
|
|
// Error Handling
|
|
AchievementId::ErrorHunter,
|
|
AchievementId::ExceptionSlayer,
|
|
AchievementId::BugExterminator,
|
|
// Refactoring
|
|
AchievementId::CleanCoder,
|
|
AchievementId::Optimizer,
|
|
AchievementId::Simplifier,
|
|
// Testing
|
|
AchievementId::TestNovice,
|
|
AchievementId::TestEnthusiast,
|
|
AchievementId::TestMaster,
|
|
AchievementId::CoverageKing,
|
|
// Documentation
|
|
AchievementId::Documenter,
|
|
AchievementId::CommentWriter,
|
|
AchievementId::ReadmeHero,
|
|
// API & Integration
|
|
AchievementId::ApiExplorer,
|
|
AchievementId::DatabaseDev,
|
|
AchievementId::CloudCoder,
|
|
// Special Milestones
|
|
AchievementId::CenturyClub,
|
|
AchievementId::ThousandSessions,
|
|
AchievementId::Veteran,
|
|
AchievementId::OldTimer,
|
|
AchievementId::Loyalist,
|
|
// Fun & Easter Eggs
|
|
AchievementId::Perfectionist,
|
|
AchievementId::Persistent,
|
|
AchievementId::Patient,
|
|
AchievementId::Speedy,
|
|
AchievementId::MultiTasker,
|
|
AchievementId::Minimalist,
|
|
AchievementId::PrivacyFirst,
|
|
AchievementId::ThemeChanger,
|
|
AchievementId::SettingsTweaker,
|
|
AchievementId::AchievementHunter,
|
|
AchievementId::Completionist,
|
|
AchievementId::MasterUnlocker,
|
|
AchievementId::PlatinumStatus,
|
|
// Clipboard & Snippets
|
|
AchievementId::ClipboardCollector,
|
|
AchievementId::SnippetCreator,
|
|
AchievementId::SnippetMaster,
|
|
AchievementId::QuickActionUser,
|
|
// Session History
|
|
AchievementId::HistoryBuff,
|
|
AchievementId::Archivist,
|
|
AchievementId::SessionExporter,
|
|
// New Features
|
|
AchievementId::GitPanelUser,
|
|
AchievementId::FeatureExplorer,
|
|
]
|
|
}
|
|
|
|
// Check achievements based on message content
|
|
pub fn check_message_achievements(
|
|
message: &str,
|
|
progress: &mut AchievementProgress,
|
|
) -> Vec<AchievementId> {
|
|
let mut newly_unlocked = Vec::new();
|
|
let message_lower = message.to_lowercase();
|
|
|
|
println!("Checking message achievements for: {}", message);
|
|
|
|
// Relationship & Greetings
|
|
if message_lower.contains("good morning") && progress.unlock(AchievementId::GoodMorning) {
|
|
newly_unlocked.push(AchievementId::GoodMorning);
|
|
}
|
|
if (message_lower.contains("good night") || message_lower.contains("goodnight"))
|
|
&& progress.unlock(AchievementId::GoodNight)
|
|
{
|
|
newly_unlocked.push(AchievementId::GoodNight);
|
|
}
|
|
if (message_lower.contains("thank you")
|
|
|| message_lower.contains("thanks")
|
|
|| message_lower.contains("thx"))
|
|
&& progress.unlock(AchievementId::ThankYou)
|
|
{
|
|
newly_unlocked.push(AchievementId::ThankYou);
|
|
}
|
|
if (message_lower.contains("love you") || message_lower.contains("ily"))
|
|
&& progress.unlock(AchievementId::LoveYou)
|
|
{
|
|
newly_unlocked.push(AchievementId::LoveYou);
|
|
}
|
|
|
|
// Personality & Fun
|
|
if message.chars().any(|c| c as u32 >= 0x1F300) && progress.unlock(AchievementId::EmojiUser) {
|
|
newly_unlocked.push(AchievementId::EmojiUser);
|
|
}
|
|
if message == message.to_uppercase()
|
|
&& message.len() > 5
|
|
&& message.chars().any(|c| c.is_alphabetic())
|
|
&& progress.unlock(AchievementId::CapsLock)
|
|
{
|
|
newly_unlocked.push(AchievementId::CapsLock);
|
|
}
|
|
if message_lower.contains("please") && progress.unlock(AchievementId::PleaseAndThankYou) {
|
|
newly_unlocked.push(AchievementId::PleaseAndThankYou);
|
|
}
|
|
|
|
// Git & Development patterns in messages
|
|
if (message_lower.contains("fix")
|
|
|| message_lower.contains("bug")
|
|
|| message_lower.contains("error"))
|
|
&& progress.unlock(AchievementId::Debugger)
|
|
{
|
|
newly_unlocked.push(AchievementId::Debugger);
|
|
}
|
|
|
|
// Extended greetings
|
|
if (message_lower.contains("hello hikari") || message_lower.contains("hi hikari"))
|
|
&& progress.unlock(AchievementId::HelloHikari)
|
|
{
|
|
newly_unlocked.push(AchievementId::HelloHikari);
|
|
}
|
|
if message_lower.contains("how are you") && progress.unlock(AchievementId::HowAreYou) {
|
|
newly_unlocked.push(AchievementId::HowAreYou);
|
|
}
|
|
if message_lower.contains("missed you") && progress.unlock(AchievementId::MissedYou) {
|
|
newly_unlocked.push(AchievementId::MissedYou);
|
|
}
|
|
if (message_lower.contains("i'm back") || message_lower.contains("back again"))
|
|
&& progress.unlock(AchievementId::BackAgain)
|
|
{
|
|
newly_unlocked.push(AchievementId::BackAgain);
|
|
}
|
|
|
|
// Emotional achievements
|
|
if (message_lower.contains("frustrated")
|
|
|| message_lower.contains("ugh")
|
|
|| message_lower.contains("argh"))
|
|
&& progress.unlock(AchievementId::Frustrated)
|
|
{
|
|
newly_unlocked.push(AchievementId::Frustrated);
|
|
}
|
|
if (message_lower.contains("excited")
|
|
|| message_lower.contains("yay")
|
|
|| message_lower.contains("woohoo"))
|
|
&& progress.unlock(AchievementId::Excited)
|
|
{
|
|
newly_unlocked.push(AchievementId::Excited);
|
|
}
|
|
if (message_lower.contains("confused") || message_lower.contains("don't understand"))
|
|
&& progress.unlock(AchievementId::Confused)
|
|
{
|
|
newly_unlocked.push(AchievementId::Confused);
|
|
}
|
|
if (message_lower.contains("why ") || message_lower.contains("how does"))
|
|
&& progress.unlock(AchievementId::Curious)
|
|
{
|
|
newly_unlocked.push(AchievementId::Curious);
|
|
}
|
|
if (message_lower.contains("wow")
|
|
|| message_lower.contains("amazing")
|
|
|| message_lower.contains("incredible"))
|
|
&& progress.unlock(AchievementId::Impressed)
|
|
{
|
|
newly_unlocked.push(AchievementId::Impressed);
|
|
}
|
|
|
|
// Message content achievements
|
|
if message.len() > 500 && progress.unlock(AchievementId::LongMessage) {
|
|
newly_unlocked.push(AchievementId::LongMessage);
|
|
}
|
|
if message.len() > 2000 && progress.unlock(AchievementId::NovelWriter) {
|
|
newly_unlocked.push(AchievementId::NovelWriter);
|
|
}
|
|
// Code in message (triple backticks)
|
|
if message.contains("```") && progress.unlock(AchievementId::CodeInMessage) {
|
|
newly_unlocked.push(AchievementId::CodeInMessage);
|
|
}
|
|
// Markdown formatting
|
|
if (message.contains("**")
|
|
|| message.contains("__")
|
|
|| message.contains("##")
|
|
|| message.contains("- ")
|
|
|| message.contains("1. "))
|
|
&& progress.unlock(AchievementId::MarkdownMaster)
|
|
{
|
|
newly_unlocked.push(AchievementId::MarkdownMaster);
|
|
}
|
|
|
|
// Refactoring keywords
|
|
if message_lower.contains("refactor") && progress.unlock(AchievementId::CleanCoder) {
|
|
newly_unlocked.push(AchievementId::CleanCoder);
|
|
}
|
|
if (message_lower.contains("optimize") || message_lower.contains("performance"))
|
|
&& progress.unlock(AchievementId::Optimizer)
|
|
{
|
|
newly_unlocked.push(AchievementId::Optimizer);
|
|
}
|
|
if message_lower.contains("simplify") && progress.unlock(AchievementId::Simplifier) {
|
|
newly_unlocked.push(AchievementId::Simplifier);
|
|
}
|
|
|
|
// Testing keywords
|
|
if message_lower.contains("test") && progress.unlock(AchievementId::TestWriter) {
|
|
newly_unlocked.push(AchievementId::TestWriter);
|
|
}
|
|
if message_lower.contains("coverage") && progress.unlock(AchievementId::CoverageKing) {
|
|
newly_unlocked.push(AchievementId::CoverageKing);
|
|
}
|
|
|
|
// Documentation keywords
|
|
if (message_lower.contains("document") || message_lower.contains("documentation"))
|
|
&& progress.unlock(AchievementId::Documenter)
|
|
{
|
|
newly_unlocked.push(AchievementId::Documenter);
|
|
}
|
|
if message_lower.contains("comment") && progress.unlock(AchievementId::CommentWriter) {
|
|
newly_unlocked.push(AchievementId::CommentWriter);
|
|
}
|
|
if message_lower.contains("readme") && progress.unlock(AchievementId::ReadmeHero) {
|
|
newly_unlocked.push(AchievementId::ReadmeHero);
|
|
}
|
|
|
|
// API & Integration keywords
|
|
if message_lower.contains("api") && progress.unlock(AchievementId::ApiExplorer) {
|
|
newly_unlocked.push(AchievementId::ApiExplorer);
|
|
}
|
|
if (message_lower.contains("database")
|
|
|| message_lower.contains("sql")
|
|
|| message_lower.contains("postgres")
|
|
|| message_lower.contains("mysql"))
|
|
&& progress.unlock(AchievementId::DatabaseDev)
|
|
{
|
|
newly_unlocked.push(AchievementId::DatabaseDev);
|
|
}
|
|
if (message_lower.contains("cloud")
|
|
|| message_lower.contains("aws")
|
|
|| message_lower.contains("azure")
|
|
|| message_lower.contains("gcp"))
|
|
&& progress.unlock(AchievementId::CloudCoder)
|
|
{
|
|
newly_unlocked.push(AchievementId::CloudCoder);
|
|
}
|
|
|
|
// Git keywords
|
|
if message_lower.contains("merge conflict") && progress.unlock(AchievementId::ConflictResolver)
|
|
{
|
|
newly_unlocked.push(AchievementId::ConflictResolver);
|
|
}
|
|
|
|
newly_unlocked
|
|
}
|
|
|
|
// Check which achievements should be unlocked based on current stats
|
|
pub fn check_achievements(
|
|
stats: &crate::stats::UsageStats,
|
|
progress: &mut AchievementProgress,
|
|
) -> Vec<AchievementId> {
|
|
let mut newly_unlocked = Vec::new();
|
|
|
|
println!(
|
|
"Checking achievements with stats: messages={}, tokens={}, code_blocks={}",
|
|
stats.messages_exchanged,
|
|
stats.total_input_tokens + stats.total_output_tokens,
|
|
stats.code_blocks_generated
|
|
);
|
|
println!("Currently unlocked: {:?}", progress.unlocked);
|
|
|
|
// Token milestones
|
|
let total_tokens = stats.total_input_tokens + stats.total_output_tokens;
|
|
if total_tokens >= 1_000 && progress.unlock(AchievementId::FirstSteps) {
|
|
println!("Unlocked FirstSteps achievement!");
|
|
newly_unlocked.push(AchievementId::FirstSteps);
|
|
}
|
|
if total_tokens >= 10_000 && progress.unlock(AchievementId::GrowingStrong) {
|
|
newly_unlocked.push(AchievementId::GrowingStrong);
|
|
}
|
|
if total_tokens >= 100_000 && progress.unlock(AchievementId::BlossomingCoder) {
|
|
newly_unlocked.push(AchievementId::BlossomingCoder);
|
|
}
|
|
if total_tokens >= 1_000_000 && progress.unlock(AchievementId::TokenMaster) {
|
|
newly_unlocked.push(AchievementId::TokenMaster);
|
|
}
|
|
|
|
// Code generation
|
|
if stats.code_blocks_generated >= 1 && progress.unlock(AchievementId::HelloWorld) {
|
|
newly_unlocked.push(AchievementId::HelloWorld);
|
|
}
|
|
if stats.code_blocks_generated >= 100 && progress.unlock(AchievementId::CodeWizard) {
|
|
newly_unlocked.push(AchievementId::CodeWizard);
|
|
}
|
|
if stats.code_blocks_generated >= 1000 && progress.unlock(AchievementId::ThousandBlocks) {
|
|
newly_unlocked.push(AchievementId::ThousandBlocks);
|
|
}
|
|
|
|
// File operations
|
|
if stats.files_edited >= 10 && progress.unlock(AchievementId::FileManipulator) {
|
|
newly_unlocked.push(AchievementId::FileManipulator);
|
|
}
|
|
let total_files = stats.files_edited + stats.files_created;
|
|
if total_files >= 100 && progress.unlock(AchievementId::FileArchitect) {
|
|
newly_unlocked.push(AchievementId::FileArchitect);
|
|
}
|
|
|
|
// Conversation milestones
|
|
if stats.messages_exchanged >= 1 && progress.unlock(AchievementId::FirstMessage) {
|
|
newly_unlocked.push(AchievementId::FirstMessage);
|
|
}
|
|
if stats.messages_exchanged >= 10 && progress.unlock(AchievementId::ConversationStarter) {
|
|
newly_unlocked.push(AchievementId::ConversationStarter);
|
|
}
|
|
if stats.messages_exchanged >= 100 && progress.unlock(AchievementId::ChattyKathy) {
|
|
newly_unlocked.push(AchievementId::ChattyKathy);
|
|
}
|
|
if stats.messages_exchanged >= 1000 && progress.unlock(AchievementId::Conversationalist) {
|
|
newly_unlocked.push(AchievementId::Conversationalist);
|
|
}
|
|
|
|
// Tool usage
|
|
let unique_tools = stats.tools_usage.len();
|
|
if unique_tools >= 5 && progress.unlock(AchievementId::Toolsmith) {
|
|
newly_unlocked.push(AchievementId::Toolsmith);
|
|
}
|
|
if unique_tools >= 10 && progress.unlock(AchievementId::ToolMaster) {
|
|
newly_unlocked.push(AchievementId::ToolMaster);
|
|
}
|
|
|
|
// Search and exploration
|
|
let search_tools = ["Glob", "Grep", "search", "Task"];
|
|
let search_count: u64 = search_tools
|
|
.iter()
|
|
.filter_map(|tool| stats.tools_usage.get(*tool))
|
|
.sum();
|
|
if search_count >= 50 && progress.unlock(AchievementId::Explorer) {
|
|
newly_unlocked.push(AchievementId::Explorer);
|
|
}
|
|
if search_count >= 500 && progress.unlock(AchievementId::MasterSearcher) {
|
|
newly_unlocked.push(AchievementId::MasterSearcher);
|
|
}
|
|
|
|
// Session duration achievements
|
|
let session_secs = stats.session_duration_seconds;
|
|
if session_secs < 300
|
|
&& stats.session_messages_exchanged >= 5
|
|
&& progress.unlock(AchievementId::QuickSession)
|
|
{
|
|
newly_unlocked.push(AchievementId::QuickSession);
|
|
}
|
|
if session_secs >= 1800 && progress.unlock(AchievementId::FocusedWork) {
|
|
newly_unlocked.push(AchievementId::FocusedWork);
|
|
}
|
|
if session_secs >= 7200 && progress.unlock(AchievementId::DeepDive) {
|
|
newly_unlocked.push(AchievementId::DeepDive);
|
|
}
|
|
if session_secs >= 18000 && progress.unlock(AchievementId::MarathonSession) {
|
|
newly_unlocked.push(AchievementId::MarathonSession);
|
|
}
|
|
|
|
// Session token achievement
|
|
let session_tokens = stats.session_input_tokens + stats.session_output_tokens;
|
|
if session_tokens >= 10000 && progress.unlock(AchievementId::MarathonCoder) {
|
|
newly_unlocked.push(AchievementId::MarathonCoder);
|
|
}
|
|
|
|
// Special first-time achievements
|
|
if !stats.tools_usage.is_empty() && progress.unlock(AchievementId::FirstTool) {
|
|
newly_unlocked.push(AchievementId::FirstTool);
|
|
}
|
|
if stats.code_blocks_generated >= 1 && progress.unlock(AchievementId::FirstCodeBlock) {
|
|
newly_unlocked.push(AchievementId::FirstCodeBlock);
|
|
}
|
|
if stats.files_edited >= 1 && progress.unlock(AchievementId::FirstFileEdit) {
|
|
newly_unlocked.push(AchievementId::FirstFileEdit);
|
|
}
|
|
|
|
// Speed coder - need to track time for this
|
|
// TODO: Implement tracking for 10 code blocks in 10 minutes
|
|
|
|
// Polyglot - need to track languages
|
|
// TODO: Implement tracking for multiple programming languages
|
|
|
|
// Claude Connoisseur - check model usage
|
|
// TODO: Track different Claude models used
|
|
|
|
// Tool mastery achievements
|
|
if let Some(bash_count) = stats.tools_usage.get("Bash") {
|
|
if *bash_count >= 50 && progress.unlock(AchievementId::BashMaster) {
|
|
newly_unlocked.push(AchievementId::BashMaster);
|
|
}
|
|
}
|
|
if let Some(read_count) = stats.tools_usage.get("Read") {
|
|
if *read_count >= 100 && progress.unlock(AchievementId::FileExplorer) {
|
|
newly_unlocked.push(AchievementId::FileExplorer);
|
|
}
|
|
}
|
|
if let Some(grep_count) = stats.tools_usage.get("Grep") {
|
|
if *grep_count >= 50 && progress.unlock(AchievementId::SearchExpert) {
|
|
newly_unlocked.push(AchievementId::SearchExpert);
|
|
}
|
|
}
|
|
|
|
// Git Guru - check git command usage in Bash
|
|
if let Some(bash_count) = stats.tools_usage.get("Bash") {
|
|
if *bash_count >= 10 && progress.unlock(AchievementId::GitGuru) {
|
|
// TODO: More specific git command tracking
|
|
newly_unlocked.push(AchievementId::GitGuru);
|
|
}
|
|
}
|
|
|
|
// Extended token milestones
|
|
if total_tokens >= 10_000_000 && progress.unlock(AchievementId::TokenBillionaire) {
|
|
newly_unlocked.push(AchievementId::TokenBillionaire);
|
|
}
|
|
if total_tokens >= 50_000_000 && progress.unlock(AchievementId::TokenTreasure) {
|
|
newly_unlocked.push(AchievementId::TokenTreasure);
|
|
}
|
|
|
|
// Extended code generation
|
|
if stats.code_blocks_generated >= 5000 && progress.unlock(AchievementId::CodeFactory) {
|
|
newly_unlocked.push(AchievementId::CodeFactory);
|
|
}
|
|
if stats.code_blocks_generated >= 10000 && progress.unlock(AchievementId::CodeEmpire) {
|
|
newly_unlocked.push(AchievementId::CodeEmpire);
|
|
}
|
|
|
|
// Extended file operations
|
|
if total_files >= 500 && progress.unlock(AchievementId::FileEngineer) {
|
|
newly_unlocked.push(AchievementId::FileEngineer);
|
|
}
|
|
if total_files >= 1000 && progress.unlock(AchievementId::FileLegend) {
|
|
newly_unlocked.push(AchievementId::FileLegend);
|
|
}
|
|
|
|
// Extended conversation milestones
|
|
if stats.messages_exchanged >= 5000 && progress.unlock(AchievementId::ChatMarathon) {
|
|
newly_unlocked.push(AchievementId::ChatMarathon);
|
|
}
|
|
if stats.messages_exchanged >= 10000 && progress.unlock(AchievementId::ChatLegend) {
|
|
newly_unlocked.push(AchievementId::ChatLegend);
|
|
}
|
|
|
|
// Extended session duration achievements
|
|
if session_secs >= 28800 && progress.unlock(AchievementId::UltraMarathon) {
|
|
// 8 hours
|
|
newly_unlocked.push(AchievementId::UltraMarathon);
|
|
}
|
|
if session_secs >= 43200 && progress.unlock(AchievementId::CodingRetreat) {
|
|
// 12 hours
|
|
newly_unlocked.push(AchievementId::CodingRetreat);
|
|
}
|
|
|
|
// More tool mastery achievements
|
|
if let Some(edit_count) = stats.tools_usage.get("Edit") {
|
|
if *edit_count >= 100 && progress.unlock(AchievementId::EditMaster) {
|
|
newly_unlocked.push(AchievementId::EditMaster);
|
|
}
|
|
}
|
|
if let Some(write_count) = stats.tools_usage.get("Write") {
|
|
if *write_count >= 50 && progress.unlock(AchievementId::WriteMaster) {
|
|
newly_unlocked.push(AchievementId::WriteMaster);
|
|
}
|
|
}
|
|
if let Some(glob_count) = stats.tools_usage.get("Glob") {
|
|
if *glob_count >= 100 && progress.unlock(AchievementId::GlobMaster) {
|
|
newly_unlocked.push(AchievementId::GlobMaster);
|
|
}
|
|
}
|
|
if let Some(task_count) = stats.tools_usage.get("Task") {
|
|
if *task_count >= 50 && progress.unlock(AchievementId::TaskMaster) {
|
|
newly_unlocked.push(AchievementId::TaskMaster);
|
|
}
|
|
}
|
|
if let Some(web_count) = stats.tools_usage.get("WebFetch") {
|
|
if *web_count >= 20 && progress.unlock(AchievementId::WebFetcher) {
|
|
newly_unlocked.push(AchievementId::WebFetcher);
|
|
}
|
|
}
|
|
// MCP tools - count tools that contain "mcp__"
|
|
let mcp_count: u64 = stats
|
|
.tools_usage
|
|
.iter()
|
|
.filter(|(name, _)| name.starts_with("mcp__"))
|
|
.map(|(_, count)| count)
|
|
.sum();
|
|
if mcp_count >= 50 && progress.unlock(AchievementId::McpExplorer) {
|
|
newly_unlocked.push(AchievementId::McpExplorer);
|
|
}
|
|
|
|
// Time-based achievements
|
|
if let Some(session_start) = progress.session_start {
|
|
let hour = session_start.hour();
|
|
let weekday = session_start.weekday();
|
|
|
|
// Early bird - 5 AM to 7 AM
|
|
if (5..=7).contains(&hour) && progress.unlock(AchievementId::EarlyBird) {
|
|
newly_unlocked.push(AchievementId::EarlyBird);
|
|
}
|
|
|
|
// Night owl - after midnight
|
|
let current_hour = Utc::now().hour();
|
|
if current_hour < 6 && progress.unlock(AchievementId::NightOwl) {
|
|
newly_unlocked.push(AchievementId::NightOwl);
|
|
}
|
|
|
|
// All nighter - 2 AM to 5 AM
|
|
if (2..=5).contains(¤t_hour) && progress.unlock(AchievementId::AllNighter) {
|
|
newly_unlocked.push(AchievementId::AllNighter);
|
|
}
|
|
|
|
// Weekend warrior
|
|
use chrono::Weekday;
|
|
if (weekday == Weekday::Sat || weekday == Weekday::Sun)
|
|
&& progress.unlock(AchievementId::WeekendWarrior)
|
|
{
|
|
newly_unlocked.push(AchievementId::WeekendWarrior);
|
|
}
|
|
|
|
// Day-specific achievements
|
|
if weekday == Weekday::Mon && progress.unlock(AchievementId::MondayMotivation) {
|
|
newly_unlocked.push(AchievementId::MondayMotivation);
|
|
}
|
|
if weekday == Weekday::Wed && progress.unlock(AchievementId::HumpDay) {
|
|
newly_unlocked.push(AchievementId::HumpDay);
|
|
}
|
|
if weekday == Weekday::Fri && progress.unlock(AchievementId::FridayFinisher) {
|
|
newly_unlocked.push(AchievementId::FridayFinisher);
|
|
}
|
|
|
|
// Time of day achievements
|
|
if (12..=13).contains(&hour) && progress.unlock(AchievementId::LunchBreakCoder) {
|
|
newly_unlocked.push(AchievementId::LunchBreakCoder);
|
|
}
|
|
if (15..=16).contains(&hour) && progress.unlock(AchievementId::CoffeeTime) {
|
|
newly_unlocked.push(AchievementId::CoffeeTime);
|
|
}
|
|
|
|
// Seasonal/special day achievements
|
|
let now = Utc::now();
|
|
let month = now.month();
|
|
let day = now.day();
|
|
|
|
// New Year's Day (January 1st)
|
|
if month == 1 && day == 1 && progress.unlock(AchievementId::NewYearCoder) {
|
|
newly_unlocked.push(AchievementId::NewYearCoder);
|
|
}
|
|
// Valentine's Day (February 14th)
|
|
if month == 2 && day == 14 && progress.unlock(AchievementId::ValentinesDev) {
|
|
newly_unlocked.push(AchievementId::ValentinesDev);
|
|
}
|
|
// Leap Day (February 29th)
|
|
if month == 2 && day == 29 && progress.unlock(AchievementId::LeapDayCoder) {
|
|
newly_unlocked.push(AchievementId::LeapDayCoder);
|
|
}
|
|
// Halloween (October 31st)
|
|
if month == 10 && day == 31 && progress.unlock(AchievementId::SpookyCode) {
|
|
newly_unlocked.push(AchievementId::SpookyCode);
|
|
}
|
|
// Christmas (December 25th)
|
|
if month == 12 && day == 25 && progress.unlock(AchievementId::HolidayCoder) {
|
|
newly_unlocked.push(AchievementId::HolidayCoder);
|
|
}
|
|
}
|
|
|
|
// Session count achievements (from stats)
|
|
if stats.sessions_started >= 100 && progress.unlock(AchievementId::CenturyClub) {
|
|
newly_unlocked.push(AchievementId::CenturyClub);
|
|
}
|
|
if stats.sessions_started >= 1000 && progress.unlock(AchievementId::ThousandSessions) {
|
|
newly_unlocked.push(AchievementId::ThousandSessions);
|
|
}
|
|
|
|
// Daily streaks (need to check stats for consecutive days)
|
|
if stats.consecutive_days >= 7 && progress.unlock(AchievementId::WeekStreak) {
|
|
newly_unlocked.push(AchievementId::WeekStreak);
|
|
}
|
|
if stats.consecutive_days >= 14 && progress.unlock(AchievementId::TwoWeekStreak) {
|
|
newly_unlocked.push(AchievementId::TwoWeekStreak);
|
|
}
|
|
if stats.consecutive_days >= 30 && progress.unlock(AchievementId::DedicatedDeveloper) {
|
|
newly_unlocked.push(AchievementId::DedicatedDeveloper);
|
|
}
|
|
if stats.consecutive_days >= 30 && progress.unlock(AchievementId::MonthStreak) {
|
|
newly_unlocked.push(AchievementId::MonthStreak);
|
|
}
|
|
if stats.consecutive_days >= 90 && progress.unlock(AchievementId::QuarterStreak) {
|
|
newly_unlocked.push(AchievementId::QuarterStreak);
|
|
}
|
|
|
|
// Total days used (Veteran/OldTimer/Loyalist)
|
|
if stats.total_days_used >= 30 && progress.unlock(AchievementId::Veteran) {
|
|
newly_unlocked.push(AchievementId::Veteran);
|
|
}
|
|
if stats.total_days_used >= 90 && progress.unlock(AchievementId::OldTimer) {
|
|
newly_unlocked.push(AchievementId::OldTimer);
|
|
}
|
|
if stats.total_days_used >= 365 && progress.unlock(AchievementId::Loyalist) {
|
|
newly_unlocked.push(AchievementId::Loyalist);
|
|
}
|
|
|
|
// Morning/Night person tracking (from session start times stored in stats)
|
|
if stats.morning_sessions >= 10 && progress.unlock(AchievementId::MorningPerson) {
|
|
newly_unlocked.push(AchievementId::MorningPerson);
|
|
}
|
|
if stats.night_sessions >= 10 && progress.unlock(AchievementId::NightCoder) {
|
|
newly_unlocked.push(AchievementId::NightCoder);
|
|
}
|
|
|
|
// Completion percentage achievements
|
|
let total_achievements = get_all_achievement_ids().len();
|
|
let unlocked_count = progress.unlocked.len();
|
|
let completion_pct = (unlocked_count * 100) / total_achievements;
|
|
|
|
if completion_pct >= 50 && progress.unlock(AchievementId::Completionist) {
|
|
newly_unlocked.push(AchievementId::Completionist);
|
|
}
|
|
if completion_pct >= 75 && progress.unlock(AchievementId::MasterUnlocker) {
|
|
newly_unlocked.push(AchievementId::MasterUnlocker);
|
|
}
|
|
if completion_pct >= 100 && progress.unlock(AchievementId::PlatinumStatus) {
|
|
newly_unlocked.push(AchievementId::PlatinumStatus);
|
|
}
|
|
|
|
newly_unlocked
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AchievementUnlockedEvent {
|
|
pub achievement: Achievement,
|
|
}
|
|
|
|
// Save achievements to persistent store
|
|
pub async fn save_achievements(
|
|
app: &tauri::AppHandle,
|
|
progress: &AchievementProgress,
|
|
) -> Result<(), String> {
|
|
let store = app.store("achievements.json").map_err(|e| e.to_string())?;
|
|
|
|
// Create a serializable version with just the unlocked achievement IDs
|
|
let unlocked_list: Vec<AchievementId> = progress.unlocked.iter().cloned().collect();
|
|
|
|
println!("Saving achievements: {:?}", unlocked_list);
|
|
|
|
store.set(
|
|
"unlocked",
|
|
serde_json::to_value(unlocked_list).map_err(|e| e.to_string())?,
|
|
);
|
|
store.save().map_err(|e| e.to_string())?;
|
|
|
|
println!("Achievements saved successfully");
|
|
Ok(())
|
|
}
|
|
|
|
// Load achievements from persistent store
|
|
pub async fn load_achievements(app: &tauri::AppHandle) -> AchievementProgress {
|
|
println!("Loading achievements from store...");
|
|
|
|
let store = match app.store("achievements.json") {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
println!("Failed to open achievements store: {}", e);
|
|
return AchievementProgress::new();
|
|
}
|
|
};
|
|
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// Get unlocked achievements
|
|
if let Some(unlocked_value) = store.get("unlocked") {
|
|
println!("Found unlocked value in store: {:?}", unlocked_value);
|
|
if let Ok(unlocked_list) =
|
|
serde_json::from_value::<Vec<AchievementId>>(unlocked_value.clone())
|
|
{
|
|
println!("Loaded {} achievements", unlocked_list.len());
|
|
for achievement_id in unlocked_list {
|
|
progress.unlocked.insert(achievement_id);
|
|
}
|
|
} else {
|
|
println!("Failed to parse unlocked achievements");
|
|
}
|
|
} else {
|
|
println!("No unlocked achievements found in store");
|
|
}
|
|
|
|
progress
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::stats::UsageStats;
|
|
use std::collections::HashMap;
|
|
|
|
// Helper function to create a default UsageStats for testing
|
|
fn create_test_stats() -> UsageStats {
|
|
UsageStats {
|
|
total_input_tokens: 0,
|
|
total_output_tokens: 0,
|
|
total_cost_usd: 0.0,
|
|
session_input_tokens: 0,
|
|
session_output_tokens: 0,
|
|
session_cost_usd: 0.0,
|
|
model: None,
|
|
messages_exchanged: 0,
|
|
session_messages_exchanged: 0,
|
|
code_blocks_generated: 0,
|
|
session_code_blocks_generated: 0,
|
|
files_edited: 0,
|
|
session_files_edited: 0,
|
|
files_created: 0,
|
|
session_files_created: 0,
|
|
tools_usage: HashMap::new(),
|
|
session_tools_usage: HashMap::new(),
|
|
session_duration_seconds: 0,
|
|
session_start: None,
|
|
sessions_started: 0,
|
|
consecutive_days: 0,
|
|
total_days_used: 0,
|
|
morning_sessions: 0,
|
|
night_sessions: 0,
|
|
last_session_date: None,
|
|
achievements: AchievementProgress::new(),
|
|
}
|
|
}
|
|
|
|
// =====================
|
|
// AchievementProgress tests
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_achievement_unlock() {
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// First unlock should return true
|
|
assert!(progress.unlock(AchievementId::FirstSteps));
|
|
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
|
|
|
|
// Second unlock of same achievement should return false
|
|
assert!(!progress.unlock(AchievementId::FirstSteps));
|
|
|
|
// Newly unlocked should contain the achievement
|
|
let newly = progress.take_newly_unlocked();
|
|
assert_eq!(newly.len(), 1);
|
|
assert_eq!(newly[0], AchievementId::FirstSteps);
|
|
|
|
// After taking, newly unlocked should be empty
|
|
let newly = progress.take_newly_unlocked();
|
|
assert!(newly.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_achievement_progress_new() {
|
|
let progress = AchievementProgress::new();
|
|
assert!(progress.unlocked.is_empty());
|
|
assert!(progress.newly_unlocked.is_empty());
|
|
assert!(progress.session_start.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_achievement_progress_start_session() {
|
|
let mut progress = AchievementProgress::new();
|
|
assert!(progress.session_start.is_none());
|
|
|
|
progress.start_session();
|
|
assert!(progress.session_start.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_unlocks() {
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
assert!(progress.unlock(AchievementId::FirstSteps));
|
|
assert!(progress.unlock(AchievementId::HelloWorld));
|
|
assert!(progress.unlock(AchievementId::FirstMessage));
|
|
|
|
assert_eq!(progress.unlocked.len(), 3);
|
|
assert_eq!(progress.newly_unlocked.len(), 3);
|
|
|
|
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
|
|
assert!(progress.is_unlocked(&AchievementId::HelloWorld));
|
|
assert!(progress.is_unlocked(&AchievementId::FirstMessage));
|
|
assert!(!progress.is_unlocked(&AchievementId::TokenMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_achievement_progress_default() {
|
|
let progress = AchievementProgress::default();
|
|
assert!(progress.unlocked.is_empty());
|
|
assert!(progress.newly_unlocked.is_empty());
|
|
}
|
|
|
|
// =====================
|
|
// get_achievement_info tests
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_get_achievement_info_first_steps() {
|
|
let info = get_achievement_info(&AchievementId::FirstSteps);
|
|
assert_eq!(info.name, "First Steps!");
|
|
assert_eq!(info.description, "Used 1,000 tokens");
|
|
assert_eq!(info.icon, "🌱");
|
|
assert!(info.unlocked_at.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_achievement_info_hello_world() {
|
|
let info = get_achievement_info(&AchievementId::HelloWorld);
|
|
assert_eq!(info.name, "Hello World!");
|
|
assert_eq!(info.description, "Generated your first code block");
|
|
assert_eq!(info.icon, "📝");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_achievement_info_night_owl() {
|
|
let info = get_achievement_info(&AchievementId::NightOwl);
|
|
assert_eq!(info.name, "Night Owl");
|
|
assert!(info.description.contains("midnight"));
|
|
assert_eq!(info.icon, "🦉");
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_achievement_info_love_you() {
|
|
let info = get_achievement_info(&AchievementId::LoveYou);
|
|
assert_eq!(info.name, "Love Connection");
|
|
assert!(info.description.contains("love"));
|
|
assert_eq!(info.icon, "💕");
|
|
}
|
|
|
|
// =====================
|
|
// get_all_achievement_ids tests
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_get_all_achievement_ids_not_empty() {
|
|
let ids = get_all_achievement_ids();
|
|
assert!(!ids.is_empty());
|
|
assert!(ids.len() > 100); // We have over 100 achievements
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_all_achievement_ids_contains_basics() {
|
|
let ids = get_all_achievement_ids();
|
|
assert!(ids.contains(&AchievementId::FirstSteps));
|
|
assert!(ids.contains(&AchievementId::HelloWorld));
|
|
assert!(ids.contains(&AchievementId::FirstMessage));
|
|
assert!(ids.contains(&AchievementId::NightOwl));
|
|
assert!(ids.contains(&AchievementId::PlatinumStatus));
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_all_achievement_ids_no_duplicates() {
|
|
let ids = get_all_achievement_ids();
|
|
let unique: HashSet<_> = ids.iter().collect();
|
|
assert_eq!(ids.len(), unique.len(), "There should be no duplicate achievement IDs");
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Token Milestones
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_first_steps_1000_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 500;
|
|
stats.total_output_tokens = 500;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FirstSteps));
|
|
assert!(progress.is_unlocked(&AchievementId::FirstSteps));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_growing_strong_10000_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 5000;
|
|
stats.total_output_tokens = 5000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FirstSteps));
|
|
assert!(newly.contains(&AchievementId::GrowingStrong));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_blossoming_coder_100000_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 50000;
|
|
stats.total_output_tokens = 50000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::BlossomingCoder));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_token_master_1000000_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 500000;
|
|
stats.total_output_tokens = 500000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::TokenMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_token_billionaire_10000000_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 5000000;
|
|
stats.total_output_tokens = 5000000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::TokenBillionaire));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_not_enough_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 400;
|
|
stats.total_output_tokens = 400;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(!newly.contains(&AchievementId::FirstSteps));
|
|
assert!(!progress.is_unlocked(&AchievementId::FirstSteps));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Code Generation
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_hello_world_first_code_block() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.code_blocks_generated = 1;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::HelloWorld));
|
|
assert!(newly.contains(&AchievementId::FirstCodeBlock));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_code_wizard_100_blocks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.code_blocks_generated = 100;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::CodeWizard));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_thousand_blocks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.code_blocks_generated = 1000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ThousandBlocks));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_code_factory_5000_blocks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.code_blocks_generated = 5000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::CodeFactory));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_code_empire_10000_blocks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.code_blocks_generated = 10000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::CodeEmpire));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - File Operations
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_first_file_edit() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.files_edited = 1;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FirstFileEdit));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_file_manipulator_10_files() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.files_edited = 10;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FileManipulator));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_file_architect_100_total_files() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.files_edited = 50;
|
|
stats.files_created = 50;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FileArchitect));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_file_engineer_500_files() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.files_edited = 300;
|
|
stats.files_created = 200;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FileEngineer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_file_legend_1000_files() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.files_edited = 600;
|
|
stats.files_created = 400;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FileLegend));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Conversation Milestones
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_first_message() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 1;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FirstMessage));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_conversation_starter_10_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 10;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ConversationStarter));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_chatty_kathy_100_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 100;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ChattyKathy));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_conversationalist_1000_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 1000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Conversationalist));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_chat_marathon_5000_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 5000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ChatMarathon));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_chat_legend_10000_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.messages_exchanged = 10000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ChatLegend));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Tool Usage
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_first_tool() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Read".to_string(), 1);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FirstTool));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_toolsmith_5_tools() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Read".to_string(), 1);
|
|
stats.tools_usage.insert("Write".to_string(), 1);
|
|
stats.tools_usage.insert("Edit".to_string(), 1);
|
|
stats.tools_usage.insert("Bash".to_string(), 1);
|
|
stats.tools_usage.insert("Grep".to_string(), 1);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Toolsmith));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_tool_master_10_tools() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
for i in 0..10 {
|
|
stats.tools_usage.insert(format!("Tool{}", i), 1);
|
|
}
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ToolMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_bash_master_50_uses() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Bash".to_string(), 50);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::BashMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_file_explorer_100_reads() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Read".to_string(), 100);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FileExplorer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_search_expert_50_greps() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Grep".to_string(), 50);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::SearchExpert));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_edit_master_100_edits() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Edit".to_string(), 100);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::EditMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_write_master_50_writes() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Write".to_string(), 50);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::WriteMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_glob_master_100_globs() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Glob".to_string(), 100);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::GlobMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_task_master_50_tasks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Task".to_string(), 50);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::TaskMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_web_fetcher_20_fetches() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("WebFetch".to_string(), 20);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::WebFetcher));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_mcp_explorer_50_mcp_calls() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("mcp__github__create_issue".to_string(), 25);
|
|
stats.tools_usage.insert("mcp__notion__search".to_string(), 25);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::McpExplorer));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Search and Exploration
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_explorer_50_searches() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Grep".to_string(), 30);
|
|
stats.tools_usage.insert("Glob".to_string(), 20);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Explorer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_master_searcher_500_searches() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.tools_usage.insert("Grep".to_string(), 200);
|
|
stats.tools_usage.insert("Glob".to_string(), 200);
|
|
stats.tools_usage.insert("Task".to_string(), 100);
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::MasterSearcher));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Session Duration
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_quick_session_under_5_min() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 200; // Under 5 minutes
|
|
stats.session_messages_exchanged = 5;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::QuickSession));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_quick_session_not_enough_messages() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 200;
|
|
stats.session_messages_exchanged = 3; // Less than 5 messages
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(!newly.contains(&AchievementId::QuickSession));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_focused_work_30_min() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 1800; // 30 minutes
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::FocusedWork));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_deep_dive_2_hours() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 7200; // 2 hours
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::DeepDive));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_marathon_session_5_hours() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 18000; // 5 hours
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::MarathonSession));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_ultra_marathon_8_hours() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 28800; // 8 hours
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::UltraMarathon));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_coding_retreat_12_hours() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_duration_seconds = 43200; // 12 hours
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::CodingRetreat));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_marathon_coder_10k_session_tokens() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.session_input_tokens = 5000;
|
|
stats.session_output_tokens = 5000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::MarathonCoder));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Streaks
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_week_streak() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.consecutive_days = 7;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::WeekStreak));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_two_week_streak() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.consecutive_days = 14;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::TwoWeekStreak));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_month_streak() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.consecutive_days = 30;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::DedicatedDeveloper));
|
|
assert!(newly.contains(&AchievementId::MonthStreak));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_quarter_streak() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.consecutive_days = 90;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::QuarterStreak));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Total Days Used
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_veteran_30_days() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_days_used = 30;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Veteran));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_old_timer_90_days() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_days_used = 90;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::OldTimer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_loyalist_365_days() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_days_used = 365;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Loyalist));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Sessions
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_century_club_100_sessions() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.sessions_started = 100;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::CenturyClub));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_thousand_sessions() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.sessions_started = 1000;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::ThousandSessions));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_morning_person() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.morning_sessions = 10;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::MorningPerson));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_night_coder() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.night_sessions = 10;
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::NightCoder));
|
|
}
|
|
|
|
// =====================
|
|
// check_achievements tests - Idempotency
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_idempotent_no_duplicate_unlocks() {
|
|
let mut stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
stats.total_input_tokens = 5000;
|
|
stats.total_output_tokens = 5000;
|
|
|
|
// First check
|
|
let newly1 = check_achievements(&stats, &mut progress);
|
|
assert!(newly1.contains(&AchievementId::FirstSteps));
|
|
assert!(newly1.contains(&AchievementId::GrowingStrong));
|
|
|
|
// Second check with same stats should not return same achievements
|
|
let newly2 = check_achievements(&stats, &mut progress);
|
|
assert!(!newly2.contains(&AchievementId::FirstSteps));
|
|
assert!(!newly2.contains(&AchievementId::GrowingStrong));
|
|
}
|
|
|
|
// =====================
|
|
// check_message_achievements tests
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_good_morning() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Good morning, Hikari!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::GoodMorning));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_good_night() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Good night!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::GoodNight));
|
|
|
|
// Also test "goodnight" variant
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Goodnight, sleep well!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::GoodNight));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_thank_you() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Thank you so much!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::ThankYou));
|
|
|
|
// Also test "thanks" variant
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Thanks for your help!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::ThankYou));
|
|
|
|
// Also test "thx" variant
|
|
let mut progress3 = AchievementProgress::new();
|
|
let newly3 = check_message_achievements("thx!", &mut progress3);
|
|
assert!(newly3.contains(&AchievementId::ThankYou));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_love_you() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I love you!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::LoveYou));
|
|
|
|
// Also test "ily" variant
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("ily hikari", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::LoveYou));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_emoji_user() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Hello! 🌸", &mut progress);
|
|
assert!(newly.contains(&AchievementId::EmojiUser));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_caps_lock() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("THIS IS ALL CAPS!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CapsLock));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_caps_lock_too_short() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("HI", &mut progress);
|
|
// Too short (< 5 chars)
|
|
assert!(!newly.contains(&AchievementId::CapsLock));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_caps_lock_numbers_only() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("123456", &mut progress);
|
|
// No alphabetic characters
|
|
assert!(!newly.contains(&AchievementId::CapsLock));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_please_and_thank_you() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Can you please help me?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::PleaseAndThankYou));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_debugger() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Can you help me fix this bug?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Debugger));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("There's an error in my code", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Debugger));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_hello_hikari() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Hello Hikari! How are you?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::HelloHikari));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Hi Hikari!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::HelloHikari));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_how_are_you() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("How are you today?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::HowAreYou));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_missed_you() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I missed you!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::MissedYou));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_back_again() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I'm back!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::BackAgain));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Back again!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::BackAgain));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_frustrated() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I'm so frustrated with this!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Frustrated));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Ugh, this isn't working", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Frustrated));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_excited() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I'm so excited!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Excited));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Yay! It worked!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Excited));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_confused() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("I'm confused about this", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Confused));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("I don't understand this", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Confused));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_curious() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Why does this happen?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Curious));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("How does this work?", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Curious));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_impressed() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Wow, that's amazing!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Impressed));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("That's incredible!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Impressed));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_long_message() {
|
|
let mut progress = AchievementProgress::new();
|
|
let long_message = "a".repeat(501);
|
|
let newly = check_message_achievements(&long_message, &mut progress);
|
|
assert!(newly.contains(&AchievementId::LongMessage));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_novel_writer() {
|
|
let mut progress = AchievementProgress::new();
|
|
let very_long_message = "a".repeat(2001);
|
|
let newly = check_message_achievements(&very_long_message, &mut progress);
|
|
assert!(newly.contains(&AchievementId::NovelWriter));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_code_in_message() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Here's some code:\n```rust\nfn main() {}\n```", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CodeInMessage));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_markdown_master() {
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// Bold
|
|
let newly = check_message_achievements("This is **bold** text", &mut progress);
|
|
assert!(newly.contains(&AchievementId::MarkdownMaster));
|
|
|
|
// Headers
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("## Header", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::MarkdownMaster));
|
|
|
|
// Lists
|
|
let mut progress3 = AchievementProgress::new();
|
|
let newly3 = check_message_achievements("- Item 1\n- Item 2", &mut progress3);
|
|
assert!(newly3.contains(&AchievementId::MarkdownMaster));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_refactoring() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Can you refactor this code?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CleanCoder));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_optimizer() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Can you optimize this?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Optimizer));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Let's improve performance", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::Optimizer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_simplifier() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Can you simplify this?", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Simplifier));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_test_writer() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Let's write some tests", &mut progress);
|
|
assert!(newly.contains(&AchievementId::TestWriter));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_coverage_king() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("We need better test coverage", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CoverageKing));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_documenter() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Let's write documentation", &mut progress);
|
|
assert!(newly.contains(&AchievementId::Documenter));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_comment_writer() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Add a comment here", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CommentWriter));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_readme_hero() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Update the README", &mut progress);
|
|
assert!(newly.contains(&AchievementId::ReadmeHero));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_api_explorer() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Let's call the API", &mut progress);
|
|
assert!(newly.contains(&AchievementId::ApiExplorer));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_database_dev() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Let's query the database", &mut progress);
|
|
assert!(newly.contains(&AchievementId::DatabaseDev));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Write a SQL query", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::DatabaseDev));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_cloud_coder() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Deploy to AWS", &mut progress);
|
|
assert!(newly.contains(&AchievementId::CloudCoder));
|
|
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("Set up Azure", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::CloudCoder));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_conflict_resolver() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("Help me fix this merge conflict", &mut progress);
|
|
assert!(newly.contains(&AchievementId::ConflictResolver));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_case_insensitive() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements("GOOD MORNING HIKARI!", &mut progress);
|
|
assert!(newly.contains(&AchievementId::GoodMorning));
|
|
// Note: HelloHikari requires "hello hikari" or "hi hikari" specifically
|
|
// Just "HIKARI" alone doesn't trigger it
|
|
|
|
// Test HelloHikari with all caps
|
|
let mut progress2 = AchievementProgress::new();
|
|
let newly2 = check_message_achievements("HELLO HIKARI!", &mut progress2);
|
|
assert!(newly2.contains(&AchievementId::HelloHikari));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_idempotent() {
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// First check
|
|
let newly1 = check_message_achievements("Good morning!", &mut progress);
|
|
assert!(newly1.contains(&AchievementId::GoodMorning));
|
|
|
|
// Second check should not unlock again
|
|
let newly2 = check_message_achievements("Good morning!", &mut progress);
|
|
assert!(!newly2.contains(&AchievementId::GoodMorning));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_message_achievements_multiple_in_one_message() {
|
|
let mut progress = AchievementProgress::new();
|
|
let newly = check_message_achievements(
|
|
"Hello Hikari! How are you? I'm excited to work on this test coverage! 🌸",
|
|
&mut progress
|
|
);
|
|
|
|
// Should unlock multiple achievements at once
|
|
assert!(newly.contains(&AchievementId::HelloHikari));
|
|
assert!(newly.contains(&AchievementId::HowAreYou));
|
|
assert!(newly.contains(&AchievementId::Excited));
|
|
assert!(newly.contains(&AchievementId::CoverageKing));
|
|
assert!(newly.contains(&AchievementId::TestWriter));
|
|
assert!(newly.contains(&AchievementId::EmojiUser));
|
|
}
|
|
|
|
// =====================
|
|
// Completion percentage tests
|
|
// =====================
|
|
|
|
#[test]
|
|
fn test_check_achievements_completionist_50_percent() {
|
|
let stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// Unlock 50% of achievements manually
|
|
let all_ids = get_all_achievement_ids();
|
|
let half = all_ids.len() / 2;
|
|
for id in all_ids.into_iter().take(half) {
|
|
progress.unlock(id);
|
|
}
|
|
progress.take_newly_unlocked(); // Clear newly unlocked
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::Completionist));
|
|
}
|
|
|
|
#[test]
|
|
fn test_check_achievements_master_unlocker_75_percent() {
|
|
let stats = create_test_stats();
|
|
let mut progress = AchievementProgress::new();
|
|
|
|
// Unlock 75% of achievements manually
|
|
let all_ids = get_all_achievement_ids();
|
|
let three_quarters = (all_ids.len() * 3) / 4;
|
|
for id in all_ids.into_iter().take(three_quarters) {
|
|
progress.unlock(id);
|
|
}
|
|
progress.take_newly_unlocked();
|
|
|
|
let newly = check_achievements(&stats, &mut progress);
|
|
assert!(newly.contains(&AchievementId::MasterUnlocker));
|
|
}
|
|
}
|