diff --git a/src-tauri/src/achievements.rs b/src-tauri/src/achievements.rs index 5497597..55cd09d 100644 --- a/src-tauri/src/achievements.rs +++ b/src-tauri/src/achievements.rs @@ -55,6 +55,28 @@ pub enum AchievementId { 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 } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -361,9 +383,164 @@ pub fn get_achievement_info(id: &AchievementId) -> Achievement { 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, + }, } } +// Check achievements based on message content +pub fn check_message_achievements( + message: &str, + progress: &mut AchievementProgress, +) -> Vec { + 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); + } + + newly_unlocked +} + // Check which achievements should be unlocked based on current stats pub fn check_achievements( stats: &crate::stats::UsageStats, @@ -489,6 +666,31 @@ pub fn check_achievements( // 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); + } + } + // Time-based achievements if let Some(session_start) = progress.session_start { let hour = session_start.hour(); diff --git a/src-tauri/src/wsl_bridge.rs b/src-tauri/src/wsl_bridge.rs index 310f2c8..3983f11 100644 --- a/src-tauri/src/wsl_bridge.rs +++ b/src-tauri/src/wsl_bridge.rs @@ -91,7 +91,7 @@ impl WslBridge { } pub async fn new_with_loaded_achievements(app: &tauri::AppHandle) -> Self { - let mut bridge = Self::new(); + let bridge = Self::new(); // Load saved achievements into the stats let achievements = crate::achievements::load_achievements(app).await; @@ -613,15 +613,35 @@ fn process_json_line(line: &str, app: &AppHandle, stats: &Arc emit_state_change(app, state, None); } - ClaudeMessage::User { .. } => { + ClaudeMessage::User { message } => { // Increment message count for user messages stats.write().increment_messages(); + // Extract text content from the message + let message_text = message.content.iter() + .filter_map(|block| match block { + crate::types::ContentBlock::Text { text } => Some(text.clone()), + _ => None, + }) + .collect::>() + .join(" "); + // Check achievements after user message let newly_unlocked = { let mut stats_guard = stats.write(); println!("User sent message, checking achievements..."); - stats_guard.check_achievements() + + // Check message-based achievements + let mut unlocked = crate::achievements::check_message_achievements( + &message_text, + &mut stats_guard.achievements, + ); + + // Check stats-based achievements + let stats_unlocked = stats_guard.check_achievements(); + unlocked.extend(stats_unlocked); + + unlocked }; // Emit achievement events for any newly unlocked achievements diff --git a/src/lib/stores/achievements.ts b/src/lib/stores/achievements.ts index ab65ce3..e0965ea 100644 --- a/src/lib/stores/achievements.ts +++ b/src/lib/stores/achievements.ts @@ -283,6 +283,154 @@ const achievementDefinitions: Record