use super::types::{ResearchId, ResearchProject};
#[derive(Debug, Clone, Default)]
pub struct ResearchService;
impl ResearchService {
pub fn new() -> Self {
Self
}
pub fn calculate_progress(
base_progress: f32,
speed_multiplier: f32,
difficulty_penalty: f32,
) -> f32 {
if difficulty_penalty <= 0.0 {
return base_progress * speed_multiplier; }
(base_progress * speed_multiplier / difficulty_penalty).max(0.0)
}
pub fn calculate_cost(base_cost: i64, tier: u32, cost_multiplier: f32) -> i64 {
let tier_scaling = (tier as f32).powi(2);
let final_cost = base_cost as f32 * tier_scaling * cost_multiplier;
final_cost.max(0.0).round() as i64
}
pub fn check_prerequisites(
required: &[ResearchId],
completed: &[ResearchId],
) -> Result<(), Vec<ResearchId>> {
let missing: Vec<ResearchId> = required
.iter()
.filter(|req| !completed.contains(req))
.cloned()
.collect();
if missing.is_empty() {
Ok(())
} else {
Err(missing)
}
}
pub fn estimate_completion(current_progress: f32, progress_per_turn: f32) -> u32 {
if current_progress >= 1.0 {
return 0; }
if progress_per_turn <= 0.0 {
return u32::MAX; }
let remaining = (1.0 - current_progress).max(0.0);
const EPSILON: f32 = 1e-5;
if remaining < EPSILON {
return 0;
}
let turns_f = remaining / progress_per_turn;
let turns_rounded = (turns_f * 1000000.0).round() / 1000000.0;
turns_rounded.ceil() as u32
}
pub fn calculate_priority(
project: &ResearchProject,
weights: &std::collections::HashMap<String, f32>,
) -> f32 {
project
.metrics
.iter()
.filter_map(|(key, value)| weights.get(key).map(|weight| value * weight))
.sum()
}
pub fn add_progress(current_progress: f32, progress_delta: f32) -> f32 {
(current_progress + progress_delta).clamp(0.0, 1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_calculate_progress() {
let progress = ResearchService::calculate_progress(0.1, 1.0, 1.0);
assert!((progress - 0.1).abs() < 0.001);
let progress = ResearchService::calculate_progress(0.1, 1.5, 1.0);
assert!((progress - 0.15).abs() < 0.001);
let progress = ResearchService::calculate_progress(0.1, 1.0, 1.5);
assert!((progress - 0.0667).abs() < 0.001);
let progress = ResearchService::calculate_progress(0.1, 1.5, 1.5);
assert!((progress - 0.1).abs() < 0.001);
let progress = ResearchService::calculate_progress(0.1, 1.5, 0.0);
assert!((progress - 0.15).abs() < 0.001);
}
#[test]
fn test_calculate_cost() {
let cost = ResearchService::calculate_cost(1000, 1, 1.0);
assert_eq!(cost, 1000);
let cost = ResearchService::calculate_cost(1000, 2, 1.0);
assert_eq!(cost, 4000);
let cost = ResearchService::calculate_cost(1000, 3, 1.0);
assert_eq!(cost, 9000);
let cost = ResearchService::calculate_cost(1000, 2, 0.5);
assert_eq!(cost, 2000);
let cost = ResearchService::calculate_cost(1000, 2, 1.5);
assert_eq!(cost, 6000);
let cost = ResearchService::calculate_cost(1000, 0, 1.0);
assert_eq!(cost, 0);
}
#[test]
fn test_check_prerequisites() {
let required = vec![ResearchId::new("writing"), ResearchId::new("philosophy")];
let completed = vec![
ResearchId::new("writing"),
ResearchId::new("philosophy"),
ResearchId::new("mathematics"),
];
let result = ResearchService::check_prerequisites(&required, &completed);
assert!(result.is_ok());
let incomplete = vec![ResearchId::new("writing")];
let result = ResearchService::check_prerequisites(&required, &incomplete);
assert!(result.is_err());
let missing = result.unwrap_err();
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].as_str(), "philosophy");
let none = vec![];
let result = ResearchService::check_prerequisites(&required, &none);
assert!(result.is_err());
let missing = result.unwrap_err();
assert_eq!(missing.len(), 2);
let empty_req = vec![];
let result = ResearchService::check_prerequisites(&empty_req, &none);
assert!(result.is_ok());
}
#[test]
fn test_estimate_completion() {
let turns = ResearchService::estimate_completion(0.5, 0.1);
assert_eq!(turns, 5);
let turns = ResearchService::estimate_completion(0.9, 0.1);
assert_eq!(turns, 1);
let turns = ResearchService::estimate_completion(1.0, 0.1);
assert_eq!(turns, 0);
let turns = ResearchService::estimate_completion(1.5, 0.1);
assert_eq!(turns, 0);
let turns = ResearchService::estimate_completion(0.75, 0.3);
assert_eq!(turns, 1);
let turns = ResearchService::estimate_completion(0.5, 0.0);
assert_eq!(turns, u32::MAX);
let turns = ResearchService::estimate_completion(0.5, -0.1);
assert_eq!(turns, u32::MAX);
}
#[test]
fn test_calculate_priority() {
let project = ResearchProject::new("advanced_tactics", "Advanced Tactics", "...")
.add_metric("military_value", 10.0)
.add_metric("economic_value", 5.0);
let mut weights = HashMap::new();
weights.insert("military_value".into(), 2.0);
weights.insert("economic_value".into(), 1.0);
let priority = ResearchService::calculate_priority(&project, &weights);
assert!((priority - 25.0).abs() < 0.001);
let empty_weights = HashMap::new();
let priority = ResearchService::calculate_priority(&project, &empty_weights);
assert!((priority - 0.0).abs() < 0.001);
let mut partial_weights = HashMap::new();
partial_weights.insert("military_value".into(), 1.5);
let priority = ResearchService::calculate_priority(&project, &partial_weights);
assert!((priority - 15.0).abs() < 0.001); }
#[test]
fn test_add_progress() {
let progress = ResearchService::add_progress(0.5, 0.3);
assert!((progress - 0.8).abs() < 0.001);
let progress = ResearchService::add_progress(0.9, 0.3);
assert_eq!(progress, 1.0);
let progress = ResearchService::add_progress(0.7, 0.3);
assert_eq!(progress, 1.0);
let progress = ResearchService::add_progress(0.2, -0.5);
assert_eq!(progress, 0.0);
let progress = ResearchService::add_progress(0.5, 0.0);
assert_eq!(progress, 0.5);
}
}