use anyhow::{bail, Result};
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum RangeOp {
LessThan(u64),
LessEqual(u64),
GreaterThan(u64),
GreaterEqual(u64),
Equal(u64),
Between(u64, u64), }
impl RangeOp {
pub fn parse(s: &str) -> Result<Self> {
let s = s.trim();
if let Some(val) = s.strip_prefix(">=") {
Ok(RangeOp::GreaterEqual(val.trim().parse()?))
} else if let Some(val) = s.strip_prefix("<=") {
Ok(RangeOp::LessEqual(val.trim().parse()?))
} else if let Some(val) = s.strip_prefix(">") {
Ok(RangeOp::GreaterThan(val.trim().parse()?))
} else if let Some(val) = s.strip_prefix("<") {
Ok(RangeOp::LessThan(val.trim().parse()?))
} else if s.contains('-') && !s.starts_with('-') {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() == 2 {
let low: u64 = parts[0].trim().parse()?;
let high: u64 = parts[1].trim().parse()?;
Ok(RangeOp::Between(low, high))
} else {
bail!("Invalid range format: {}", s)
}
} else {
Ok(RangeOp::Equal(s.parse()?))
}
}
pub fn matches(&self, value: u64) -> bool {
match self {
RangeOp::LessThan(n) => value < *n,
RangeOp::LessEqual(n) => value <= *n,
RangeOp::GreaterThan(n) => value > *n,
RangeOp::GreaterEqual(n) => value >= *n,
RangeOp::Equal(n) => value == *n,
RangeOp::Between(low, high) => value >= *low && value <= *high,
}
}
}
#[derive(Debug, Clone)]
pub enum Effect {
Add(f64),
Multiply(f64),
AddPerUnit(f64, Duration),
MultiplyPerUnit(f64, Duration),
}
impl Effect {
pub fn parse(s: &str) -> Result<Self> {
let s = s.trim();
if let Some((effect_part, per_part)) = s.split_once(" per ") {
let duration = humantime::parse_duration(per_part.trim())?;
if let Some(val) = effect_part.strip_prefix('+') {
Ok(Effect::AddPerUnit(val.trim().parse()?, duration))
} else if let Some(val) = effect_part.strip_prefix('x') {
Ok(Effect::MultiplyPerUnit(val.trim().parse()?, duration))
} else {
bail!("Effect must start with + or x: {}", s)
}
} else if let Some(val) = s.strip_prefix('+') {
Ok(Effect::Add(val.trim().parse()?))
} else if let Some(val) = s.strip_prefix('x') {
Ok(Effect::Multiply(val.trim().parse()?))
} else {
bail!("Effect must start with + or x: {}", s)
}
}
pub fn apply(&self, score: f64, units: u64) -> f64 {
match self {
Effect::Add(n) => score + n,
Effect::Multiply(n) => score * n,
Effect::AddPerUnit(n, _) => score + (n * units as f64),
Effect::MultiplyPerUnit(n, _) => score * n.powf(units as f64),
}
}
pub fn unit_duration(&self) -> Option<Duration> {
match self {
Effect::AddPerUnit(_, d) | Effect::MultiplyPerUnit(_, d) => Some(*d),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_range_less_than() {
let range = RangeOp::parse("<100").unwrap();
assert!(range.matches(50));
assert!(!range.matches(100));
assert!(!range.matches(150));
}
#[test]
fn test_parse_range_less_equal() {
let range = RangeOp::parse("<=100").unwrap();
assert!(range.matches(50));
assert!(range.matches(100));
assert!(!range.matches(101));
}
#[test]
fn test_parse_range_greater_than() {
let range = RangeOp::parse(">100").unwrap();
assert!(!range.matches(50));
assert!(!range.matches(100));
assert!(range.matches(150));
}
#[test]
fn test_parse_range_greater_equal() {
let range = RangeOp::parse(">=100").unwrap();
assert!(!range.matches(50));
assert!(range.matches(100));
assert!(range.matches(150));
}
#[test]
fn test_parse_range_equal() {
let range = RangeOp::parse("0").unwrap();
assert!(range.matches(0));
assert!(!range.matches(1));
}
#[test]
fn test_parse_range_between() {
let range = RangeOp::parse("100-500").unwrap();
assert!(!range.matches(50));
assert!(range.matches(100));
assert!(range.matches(300));
assert!(range.matches(500));
assert!(!range.matches(501));
}
#[test]
fn test_parse_effect_add() {
let effect = Effect::parse("+10").unwrap();
assert_eq!(effect.apply(100.0, 1), 110.0);
}
#[test]
fn test_parse_effect_multiply() {
let effect = Effect::parse("x2").unwrap();
assert_eq!(effect.apply(100.0, 1), 200.0);
}
#[test]
fn test_parse_effect_add_per_unit() {
let effect = Effect::parse("+1 per 1h").unwrap();
assert_eq!(effect.apply(100.0, 5), 105.0); }
#[test]
fn test_parse_effect_multiply_per_unit() {
let effect = Effect::parse("x1.1 per 1h").unwrap();
let result = effect.apply(100.0, 3); assert!((result - 133.1).abs() < 0.1); }
#[test]
fn test_parse_effect_negative_add() {
let effect = Effect::parse("+-5").unwrap();
assert_eq!(effect.apply(100.0, 1), 95.0);
}
#[test]
fn test_parse_effect_decimal_multiply() {
let effect = Effect::parse("x0.5").unwrap();
assert_eq!(effect.apply(100.0, 1), 50.0);
}
}