use super::types::*;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::time::SystemTime;
pub type Timestamp = SystemTime;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DiscoveryState {
discovered_recipes: HashMap<EntityId, HashSet<RecipeId>>,
experimentation_history: HashMap<EntityId, HashMap<Vec<IngredientType>, u32>>,
}
impl DiscoveryState {
pub fn new() -> Self {
Self::default()
}
pub fn discover_recipe(&mut self, entity_id: &EntityId, recipe_id: &RecipeId) {
self.discovered_recipes
.entry(entity_id.clone())
.or_default()
.insert(recipe_id.clone());
}
pub fn is_discovered(&self, entity_id: &EntityId, recipe_id: &RecipeId) -> bool {
self.discovered_recipes
.get(entity_id)
.map(|recipes| recipes.contains(recipe_id))
.unwrap_or(false)
}
pub fn get_discovered_recipes(&self, entity_id: &EntityId) -> HashSet<RecipeId> {
self.discovered_recipes
.get(entity_id)
.cloned()
.unwrap_or_default()
}
pub fn record_attempt(
&mut self,
entity_id: &EntityId,
ingredients: Vec<IngredientType>,
) -> u32 {
let history = self
.experimentation_history
.entry(entity_id.clone())
.or_default();
let count = history.get(&ingredients).copied().unwrap_or(0) + 1;
history.insert(ingredients, count);
count
}
pub fn get_attempt_count(&self, entity_id: &EntityId, ingredients: &[IngredientType]) -> u32 {
self.experimentation_history
.get(entity_id)
.and_then(|history| history.get(ingredients).copied())
.unwrap_or(0)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ActiveSynthesis {
pub id: SynthesisId,
pub entity_id: EntityId,
pub recipe_id: RecipeId,
pub consumed_ingredients: Vec<(IngredientType, u32)>,
pub started_at: Timestamp,
pub completes_at: Timestamp,
pub success_chance: f32,
pub status: SynthesisStatus,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SynthesisState {
active_syntheses: HashMap<SynthesisId, ActiveSynthesis>,
synthesis_queues: HashMap<EntityId, Vec<SynthesisId>>,
}
impl SynthesisState {
pub fn new() -> Self {
Self::default()
}
pub fn add_synthesis(&mut self, synthesis: ActiveSynthesis) {
let id = synthesis.id;
let entity_id = synthesis.entity_id.clone();
self.active_syntheses.insert(id, synthesis);
self.synthesis_queues.entry(entity_id).or_default().push(id);
}
pub fn get_synthesis(&self, id: &SynthesisId) -> Option<&ActiveSynthesis> {
self.active_syntheses.get(id)
}
pub fn get_synthesis_mut(&mut self, id: &SynthesisId) -> Option<&mut ActiveSynthesis> {
self.active_syntheses.get_mut(id)
}
pub fn remove_synthesis(&mut self, id: &SynthesisId) -> Option<ActiveSynthesis> {
if let Some(synthesis) = self.active_syntheses.remove(id) {
if let Some(queue) = self.synthesis_queues.get_mut(&synthesis.entity_id) {
queue.retain(|&qid| qid != *id);
}
Some(synthesis)
} else {
None
}
}
pub fn get_queue(&self, entity_id: &EntityId) -> Vec<&ActiveSynthesis> {
self.synthesis_queues
.get(entity_id)
.map(|queue| {
queue
.iter()
.filter_map(|id| self.active_syntheses.get(id))
.collect()
})
.unwrap_or_default()
}
pub fn all_syntheses(&self) -> impl Iterator<Item = (&SynthesisId, &ActiveSynthesis)> {
self.active_syntheses.iter()
}
pub fn all_syntheses_mut(
&mut self,
) -> impl Iterator<Item = (&SynthesisId, &mut ActiveSynthesis)> {
self.active_syntheses.iter_mut()
}
pub fn synthesis_count(&self) -> usize {
self.active_syntheses.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_discovery_state_new() {
let state = DiscoveryState::new();
assert!(!state.is_discovered(&"player1".to_string(), &"recipe1".to_string()));
}
#[test]
fn test_discover_recipe() {
let mut state = DiscoveryState::new();
state.discover_recipe(&"player1".to_string(), &"recipe1".to_string());
assert!(state.is_discovered(&"player1".to_string(), &"recipe1".to_string()));
assert!(!state.is_discovered(&"player2".to_string(), &"recipe1".to_string()));
assert!(!state.is_discovered(&"player1".to_string(), &"recipe2".to_string()));
}
#[test]
fn test_get_discovered_recipes() {
let mut state = DiscoveryState::new();
state.discover_recipe(&"player1".to_string(), &"recipe1".to_string());
state.discover_recipe(&"player1".to_string(), &"recipe2".to_string());
let recipes = state.get_discovered_recipes(&"player1".to_string());
assert_eq!(recipes.len(), 2);
assert!(recipes.contains("recipe1"));
assert!(recipes.contains("recipe2"));
}
#[test]
fn test_record_attempt() {
let mut state = DiscoveryState::new();
let ingredients = vec![
IngredientType::Item {
item_id: "iron".to_string(),
},
IngredientType::Item {
item_id: "wood".to_string(),
},
];
let count1 = state.record_attempt(&"player1".to_string(), ingredients.clone());
assert_eq!(count1, 1);
let count2 = state.record_attempt(&"player1".to_string(), ingredients.clone());
assert_eq!(count2, 2);
let count3 = state.get_attempt_count(&"player1".to_string(), &ingredients);
assert_eq!(count3, 2);
}
#[test]
fn test_synthesis_state_new() {
let state = SynthesisState::new();
assert_eq!(state.synthesis_count(), 0);
}
#[test]
fn test_add_and_get_synthesis() {
let mut state = SynthesisState::new();
let now = SystemTime::now();
let synthesis = ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player1".to_string(),
recipe_id: "sword".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(10),
success_chance: 0.8,
status: SynthesisStatus::InProgress,
};
let id = synthesis.id;
state.add_synthesis(synthesis);
assert_eq!(state.synthesis_count(), 1);
assert!(state.get_synthesis(&id).is_some());
}
#[test]
fn test_get_queue() {
let mut state = SynthesisState::new();
let now = SystemTime::now();
let synthesis1 = ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player1".to_string(),
recipe_id: "sword".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(10),
success_chance: 0.8,
status: SynthesisStatus::InProgress,
};
let synthesis2 = ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player1".to_string(),
recipe_id: "bow".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(20),
success_chance: 0.7,
status: SynthesisStatus::InProgress,
};
state.add_synthesis(synthesis1);
state.add_synthesis(synthesis2);
let queue = state.get_queue(&"player1".to_string());
assert_eq!(queue.len(), 2);
let empty_queue = state.get_queue(&"player2".to_string());
assert_eq!(empty_queue.len(), 0);
}
#[test]
fn test_remove_synthesis() {
let mut state = SynthesisState::new();
let now = SystemTime::now();
let synthesis = ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player1".to_string(),
recipe_id: "sword".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(10),
success_chance: 0.8,
status: SynthesisStatus::InProgress,
};
let id = synthesis.id;
state.add_synthesis(synthesis);
assert_eq!(state.synthesis_count(), 1);
let removed = state.remove_synthesis(&id);
assert!(removed.is_some());
assert_eq!(state.synthesis_count(), 0);
let queue = state.get_queue(&"player1".to_string());
assert_eq!(queue.len(), 0);
}
#[test]
fn test_all_syntheses() {
let mut state = SynthesisState::new();
let now = SystemTime::now();
state.add_synthesis(ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player1".to_string(),
recipe_id: "sword".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(10),
success_chance: 0.8,
status: SynthesisStatus::InProgress,
});
state.add_synthesis(ActiveSynthesis {
id: SynthesisId::new(),
entity_id: "player2".to_string(),
recipe_id: "bow".to_string(),
consumed_ingredients: vec![],
started_at: now,
completes_at: now + Duration::from_secs(20),
success_chance: 0.7,
status: SynthesisStatus::InProgress,
});
let all: Vec<_> = state.all_syntheses().collect();
assert_eq!(all.len(), 2);
}
}