use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AchievementTier {
Bronze,
Silver,
Gold,
Platinum,
}
impl AchievementTier {
#[must_use]
pub const fn xp_reward(&self) -> u64 {
match self {
Self::Bronze => 50,
Self::Silver => 100,
Self::Gold => 250,
Self::Platinum => 500,
}
}
#[must_use]
pub const fn css_class(&self) -> &'static str {
match self {
Self::Bronze => "achievement-bronze",
Self::Silver => "achievement-silver",
Self::Gold => "achievement-gold",
Self::Platinum => "achievement-platinum",
}
}
#[must_use]
pub const fn display_name(&self) -> &'static str {
match self {
Self::Bronze => "Bronze",
Self::Silver => "Silver",
Self::Gold => "Gold",
Self::Platinum => "Platinum",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AchievementCategory {
Discovery,
Performance,
Dedication,
Mastery,
}
impl AchievementCategory {
#[must_use]
pub const fn display_name(&self) -> &'static str {
match self {
Self::Discovery => "Discovery",
Self::Performance => "Performance",
Self::Dedication => "Dedication",
Self::Mastery => "Mastery",
}
}
#[must_use]
pub const fn icon(&self) -> char {
match self {
Self::Discovery => '?',
Self::Performance => '>',
Self::Dedication => '*',
Self::Mastery => '#',
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Achievement {
pub id: &'static str,
pub name: &'static str,
pub description: &'static str,
pub tier: AchievementTier,
pub category: AchievementCategory,
pub threshold: Option<u64>,
pub hidden: bool,
}
impl Achievement {
#[must_use]
pub const fn new(
id: &'static str,
name: &'static str,
description: &'static str,
tier: AchievementTier,
category: AchievementCategory,
) -> Self {
Self {
id,
name,
description,
tier,
category,
threshold: None,
hidden: false,
}
}
#[must_use]
pub const fn with_threshold(mut self, threshold: u64) -> Self {
self.threshold = Some(threshold);
self
}
#[must_use]
pub const fn hidden(mut self) -> Self {
self.hidden = true;
self
}
}
pub const ACHIEVEMENTS: &[Achievement] = &[
Achievement::new(
"first_query",
"First Steps",
"Execute your first query",
AchievementTier::Bronze,
AchievementCategory::Discovery,
),
Achievement::new(
"first_graph",
"Graph Explorer",
"View the graph engine for the first time",
AchievementTier::Bronze,
AchievementCategory::Discovery,
),
Achievement::new(
"first_vector",
"Vector Pioneer",
"Perform your first vector search",
AchievementTier::Bronze,
AchievementCategory::Discovery,
),
Achievement::new(
"first_algorithm",
"Algorithm Initiate",
"Run a graph algorithm",
AchievementTier::Bronze,
AchievementCategory::Discovery,
),
Achievement::new(
"all_engines",
"Triple Threat",
"Use all three engines in one session",
AchievementTier::Silver,
AchievementCategory::Discovery,
),
Achievement::new(
"fast_query",
"Speed Demon",
"Execute a query in under 10ms",
AchievementTier::Bronze,
AchievementCategory::Performance,
),
Achievement::new(
"lightning_query",
"Lightning Fast",
"Execute a query in under 1ms",
AchievementTier::Silver,
AchievementCategory::Performance,
),
Achievement::new(
"hundred_queries",
"Query Centurion",
"Execute 100 queries",
AchievementTier::Bronze,
AchievementCategory::Performance,
)
.with_threshold(100),
Achievement::new(
"thousand_queries",
"Query Master",
"Execute 1,000 queries",
AchievementTier::Silver,
AchievementCategory::Performance,
)
.with_threshold(1000),
Achievement::new(
"ten_thousand_queries",
"Query Legend",
"Execute 10,000 queries",
AchievementTier::Gold,
AchievementCategory::Performance,
)
.with_threshold(10000),
Achievement::new(
"streak_3",
"Getting Started",
"Maintain a 3-day streak",
AchievementTier::Bronze,
AchievementCategory::Dedication,
)
.with_threshold(3),
Achievement::new(
"streak_7",
"Weekly Warrior",
"Maintain a 7-day streak",
AchievementTier::Silver,
AchievementCategory::Dedication,
)
.with_threshold(7),
Achievement::new(
"streak_30",
"Monthly Maven",
"Maintain a 30-day streak",
AchievementTier::Gold,
AchievementCategory::Dedication,
)
.with_threshold(30),
Achievement::new(
"streak_100",
"Century Club",
"Maintain a 100-day streak",
AchievementTier::Platinum,
AchievementCategory::Dedication,
)
.with_threshold(100),
Achievement::new(
"all_algorithms",
"Algorithm Collector",
"Use all graph algorithms",
AchievementTier::Gold,
AchievementCategory::Mastery,
),
Achievement::new(
"pagerank_master",
"Influence Mapper",
"Run PageRank 50 times",
AchievementTier::Silver,
AchievementCategory::Mastery,
)
.with_threshold(50),
Achievement::new(
"community_finder",
"Community Builder",
"Detect communities in 10 different graphs",
AchievementTier::Silver,
AchievementCategory::Mastery,
)
.with_threshold(10),
Achievement::new(
"path_finder",
"Pathfinder",
"Find 100 shortest paths",
AchievementTier::Silver,
AchievementCategory::Mastery,
)
.with_threshold(100),
Achievement::new(
"level_10",
"Engine Master",
"Reach level 10",
AchievementTier::Gold,
AchievementCategory::Mastery,
)
.with_threshold(10),
Achievement::new(
"level_20",
"Tensor Grandmaster",
"Reach level 20",
AchievementTier::Platinum,
AchievementCategory::Mastery,
)
.with_threshold(20),
Achievement::new(
"night_owl",
"Night Owl",
"Use the system between midnight and 4am",
AchievementTier::Bronze,
AchievementCategory::Discovery,
)
.hidden(),
Achievement::new(
"early_bird",
"Early Bird",
"Use the system between 5am and 7am",
AchievementTier::Bronze,
AchievementCategory::Discovery,
)
.hidden(),
];
#[must_use]
pub fn get_achievement(id: &str) -> Option<&'static Achievement> {
ACHIEVEMENTS.iter().find(|a| a.id == id)
}
#[must_use]
pub fn achievements_by_category(category: AchievementCategory) -> Vec<&'static Achievement> {
ACHIEVEMENTS
.iter()
.filter(|a| a.category == category)
.collect()
}
#[must_use]
pub fn achievements_by_tier(tier: AchievementTier) -> Vec<&'static Achievement> {
ACHIEVEMENTS.iter().filter(|a| a.tier == tier).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tier_xp_reward() {
assert_eq!(AchievementTier::Bronze.xp_reward(), 50);
assert_eq!(AchievementTier::Silver.xp_reward(), 100);
assert_eq!(AchievementTier::Gold.xp_reward(), 250);
assert_eq!(AchievementTier::Platinum.xp_reward(), 500);
}
#[test]
fn test_tier_css_class() {
assert_eq!(AchievementTier::Bronze.css_class(), "achievement-bronze");
assert_eq!(
AchievementTier::Platinum.css_class(),
"achievement-platinum"
);
}
#[test]
fn test_category_display_name() {
assert_eq!(AchievementCategory::Discovery.display_name(), "Discovery");
assert_eq!(AchievementCategory::Mastery.display_name(), "Mastery");
}
#[test]
fn test_category_icon() {
assert_eq!(AchievementCategory::Discovery.icon(), '?');
assert_eq!(AchievementCategory::Mastery.icon(), '#');
}
#[test]
fn test_achievements_defined() {
assert!(!ACHIEVEMENTS.is_empty());
assert!(ACHIEVEMENTS.len() >= 20);
}
#[test]
fn test_get_achievement() {
let achievement = get_achievement("first_query");
assert!(achievement.is_some());
assert_eq!(achievement.unwrap().name, "First Steps");
}
#[test]
fn test_get_achievement_not_found() {
let achievement = get_achievement("nonexistent");
assert!(achievement.is_none());
}
#[test]
fn test_achievements_by_category() {
let discovery = achievements_by_category(AchievementCategory::Discovery);
assert!(!discovery.is_empty());
assert!(discovery
.iter()
.all(|a| a.category == AchievementCategory::Discovery));
}
#[test]
fn test_achievements_by_tier() {
let bronze = achievements_by_tier(AchievementTier::Bronze);
assert!(!bronze.is_empty());
assert!(bronze.iter().all(|a| a.tier == AchievementTier::Bronze));
}
#[test]
fn test_achievement_with_threshold() {
let achievement = get_achievement("hundred_queries").unwrap();
assert_eq!(achievement.threshold, Some(100));
}
#[test]
fn test_hidden_achievement() {
let achievement = get_achievement("night_owl").unwrap();
assert!(achievement.hidden);
}
#[test]
fn test_achievement_serialization() {
let achievement = get_achievement("first_query").unwrap();
let json = serde_json::to_string(achievement).expect("serialization failed");
assert!(json.contains("first_query"));
assert!(json.contains("First Steps"));
}
#[test]
fn test_tier_display_name() {
assert_eq!(AchievementTier::Bronze.display_name(), "Bronze");
assert_eq!(AchievementTier::Silver.display_name(), "Silver");
assert_eq!(AchievementTier::Gold.display_name(), "Gold");
assert_eq!(AchievementTier::Platinum.display_name(), "Platinum");
}
#[test]
fn test_tier_serialization() {
let tier = AchievementTier::Gold;
let json = serde_json::to_string(&tier).expect("serialization failed");
assert!(json.contains("gold"));
let decoded: AchievementTier = serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(decoded, AchievementTier::Gold);
}
#[test]
fn test_category_serialization() {
let category = AchievementCategory::Performance;
let json = serde_json::to_string(&category).expect("serialization failed");
assert!(json.contains("performance"));
let decoded: AchievementCategory =
serde_json::from_str(&json).expect("deserialization failed");
assert_eq!(decoded, AchievementCategory::Performance);
}
#[test]
fn test_category_icon_all() {
assert_eq!(AchievementCategory::Performance.icon(), '>');
assert_eq!(AchievementCategory::Dedication.icon(), '*');
}
#[test]
fn test_achievement_new() {
let achievement = Achievement::new(
"test_id",
"Test Name",
"Test description",
AchievementTier::Silver,
AchievementCategory::Mastery,
);
assert_eq!(achievement.id, "test_id");
assert_eq!(achievement.name, "Test Name");
assert!(!achievement.hidden);
assert!(achievement.threshold.is_none());
}
#[test]
fn test_achievement_with_threshold_builder() {
let achievement = Achievement::new(
"test",
"Test",
"Test",
AchievementTier::Bronze,
AchievementCategory::Discovery,
)
.with_threshold(50);
assert_eq!(achievement.threshold, Some(50));
}
#[test]
fn test_achievement_hidden_builder() {
let achievement = Achievement::new(
"test",
"Test",
"Test",
AchievementTier::Bronze,
AchievementCategory::Discovery,
)
.hidden();
assert!(achievement.hidden);
}
#[test]
fn test_achievement_chained_builders() {
let achievement = Achievement::new(
"chained",
"Chained",
"Chained description",
AchievementTier::Platinum,
AchievementCategory::Mastery,
)
.with_threshold(1000)
.hidden();
assert_eq!(achievement.id, "chained");
assert_eq!(achievement.tier, AchievementTier::Platinum);
assert_eq!(achievement.threshold, Some(1000));
assert!(achievement.hidden);
}
#[test]
fn test_achievements_by_category_all() {
let performance = achievements_by_category(AchievementCategory::Performance);
let dedication = achievements_by_category(AchievementCategory::Dedication);
let mastery = achievements_by_category(AchievementCategory::Mastery);
assert!(!performance.is_empty());
assert!(!dedication.is_empty());
assert!(!mastery.is_empty());
}
#[test]
fn test_achievements_by_tier_all() {
let silver = achievements_by_tier(AchievementTier::Silver);
let gold = achievements_by_tier(AchievementTier::Gold);
let platinum = achievements_by_tier(AchievementTier::Platinum);
assert!(!silver.is_empty());
assert!(!gold.is_empty());
assert!(!platinum.is_empty());
}
#[test]
fn test_all_achievements_have_valid_fields() {
for achievement in ACHIEVEMENTS {
assert!(!achievement.id.is_empty());
assert!(!achievement.name.is_empty());
assert!(!achievement.description.is_empty());
}
}
#[test]
fn test_achievements_unique_ids() {
let mut seen = std::collections::HashSet::new();
for achievement in ACHIEVEMENTS {
assert!(
seen.insert(achievement.id),
"Duplicate achievement ID: {}",
achievement.id
);
}
}
#[test]
fn test_tier_css_class_all() {
assert_eq!(AchievementTier::Silver.css_class(), "achievement-silver");
assert_eq!(AchievementTier::Gold.css_class(), "achievement-gold");
}
#[test]
fn test_category_display_name_all() {
assert_eq!(
AchievementCategory::Performance.display_name(),
"Performance"
);
assert_eq!(AchievementCategory::Dedication.display_name(), "Dedication");
}
#[test]
fn test_achievement_full_serialization() {
let achievement = get_achievement("first_query").unwrap();
let json = serde_json::to_string(achievement).expect("serialization failed");
assert!(json.contains("first_query"));
assert!(json.contains("First Steps"));
assert!(json.contains("bronze"));
assert!(json.contains("discovery"));
}
#[test]
fn test_achievement_tier_eq() {
assert_eq!(AchievementTier::Bronze, AchievementTier::Bronze);
assert_ne!(AchievementTier::Bronze, AchievementTier::Gold);
}
#[test]
fn test_achievement_category_eq() {
assert_eq!(
AchievementCategory::Discovery,
AchievementCategory::Discovery
);
assert_ne!(AchievementCategory::Discovery, AchievementCategory::Mastery);
}
#[test]
fn test_achievement_clone() {
let achievement = get_achievement("first_query").unwrap();
let cloned = achievement.clone();
assert_eq!(cloned.id, achievement.id);
assert_eq!(cloned.name, achievement.name);
}
#[test]
fn test_achievement_debug() {
let achievement = get_achievement("first_query").unwrap();
let debug_str = format!("{:?}", achievement);
assert!(debug_str.contains("Achievement"));
assert!(debug_str.contains("first_query"));
}
#[test]
fn test_tier_debug() {
let tier = AchievementTier::Platinum;
let debug_str = format!("{:?}", tier);
assert!(debug_str.contains("Platinum"));
}
#[test]
fn test_category_debug() {
let category = AchievementCategory::Mastery;
let debug_str = format!("{:?}", category);
assert!(debug_str.contains("Mastery"));
}
#[test]
fn test_tier_clone() {
let tier = AchievementTier::Gold;
let cloned = tier;
assert_eq!(cloned, tier);
}
#[test]
fn test_category_clone() {
let category = AchievementCategory::Performance;
let cloned = category;
assert_eq!(cloned, category);
}
}