ScalingTransform

Struct ScalingTransform 

Source
pub struct ScalingTransform { /* private fields */ }
Expand description

A transform that scales based on another stat.

Adds dependency_value * scale_factor to the input value. This is commonly used for derived stats (e.g., ATK = base + STR * 2).

§Examples

use zzstat::transform::{StatTransform, ScalingTransform};
use zzstat::{StatId, StatContext};
use std::collections::HashMap;

let str_id = StatId::from_str("STR");
let transform = ScalingTransform::new(str_id.clone(), 2.0);

let mut deps = HashMap::new();
deps.insert(str_id.clone(), 10.0);

let context = StatContext::new();
// 100 (base) + 10 (STR) * 2 = 120
assert_eq!(transform.apply(100.0, &deps, &context).unwrap(), 120.0);

Implementations§

Source§

impl ScalingTransform

Source

pub fn new(dependency: StatId, scale_factor: f64) -> Self

Create a new scaling transform.

§Arguments
  • dependency - The stat ID this transform depends on
  • scale_factor - The multiplier to apply to the dependency value
§Examples
use zzstat::transform::ScalingTransform;
use zzstat::StatId;

let str_id = StatId::from_str("STR");
// ATK scales with STR: ATK = base + STR * 2
let transform = ScalingTransform::new(str_id, 2.0);
Examples found in repository?
examples/dependencies.rs (line 36)
12fn main() -> Result<(), StatError> {
13    let mut resolver = StatResolver::new();
14
15    // Define stat IDs
16    let str_id = StatId::from_str("STR");
17    let dex_id = StatId::from_str("DEX");
18    let atk_id = StatId::from_str("ATK");
19    let crit_id = StatId::from_str("CRIT");
20
21    println!("=== Setting up base stats ===");
22
23    // Base stats (no dependencies)
24    resolver.register_source(str_id.clone(), Box::new(ConstantSource(10.0)));
25    println!("STR: 10 (base)");
26
27    resolver.register_source(dex_id.clone(), Box::new(ConstantSource(15.0)));
28    println!("DEX: 15 (base)");
29
30    println!("\n=== Setting up derived stats ===");
31
32    // ATK depends on STR
33    resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
34    resolver.register_transform(
35        atk_id.clone(),
36        Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
37    );
38    println!("ATK: 50 (base) + STR * 2.0");
39
40    // CRIT depends on DEX
41    resolver.register_source(crit_id.clone(), Box::new(ConstantSource(5.0)));
42    resolver.register_transform(
43        crit_id.clone(),
44        Box::new(ScalingTransform::new(dex_id.clone(), 1.5)),
45    );
46    println!("CRIT: 5 (base) + DEX * 1.5");
47
48    let context = StatContext::new();
49
50    println!("\n=== Resolving all stats ===");
51    let results = resolver.resolve_all(&context)?;
52
53    // Display results
54    println!("\n=== Results ===");
55    for (stat_id, resolved) in &results {
56        println!("\n{}:", stat_id);
57        println!("  Final Value: {:.2}", resolved.value);
58
59        if !resolved.sources.is_empty() {
60            println!("  Sources:");
61            for (desc, value) in &resolved.sources {
62                println!("    {}: {:.2}", desc, value);
63            }
64        }
65
66        if !resolved.transforms.is_empty() {
67            println!("  Transforms:");
68            for (desc, value) in &resolved.transforms {
69                println!("    {}: {:.2}", desc, value);
70            }
71        }
72    }
73
74    println!("\n=== Verification ===");
75    println!("STR: {:.2} (expected: 10.00)", results[&str_id].value);
76    println!("DEX: {:.2} (expected: 15.00)", results[&dex_id].value);
77    println!(
78        "ATK: {:.2} (expected: 70.00 = 50 + 10*2)",
79        results[&atk_id].value
80    );
81    println!(
82        "CRIT: {:.2} (expected: 27.50 = 5 + 15*1.5)",
83        results[&crit_id].value
84    );
85
86    Ok(())
87}
More examples
Hide additional examples
examples/cycle_detection.rs (line 25)
11fn main() {
12    let mut resolver = StatResolver::new();
13
14    // Create stats that depend on each other in a cycle
15    let a_id = StatId::from_str("A");
16    let b_id = StatId::from_str("B");
17    let c_id = StatId::from_str("C");
18
19    println!("=== Setting up circular dependencies ===\n");
20
21    // A depends on B
22    resolver.register_source(a_id.clone(), Box::new(ConstantSource(10.0)));
23    resolver.register_transform(
24        a_id.clone(),
25        Box::new(ScalingTransform::new(b_id.clone(), 1.0)),
26    );
27    println!("A: 10 (base) + B * 1.0");
28
29    // B depends on C
30    resolver.register_source(b_id.clone(), Box::new(ConstantSource(20.0)));
31    resolver.register_transform(
32        b_id.clone(),
33        Box::new(ScalingTransform::new(c_id.clone(), 1.0)),
34    );
35    println!("B: 20 (base) + C * 1.0");
36
37    // C depends on A (creates cycle: A -> B -> C -> A)
38    resolver.register_source(c_id.clone(), Box::new(ConstantSource(30.0)));
39    resolver.register_transform(
40        c_id.clone(),
41        Box::new(ScalingTransform::new(a_id.clone(), 1.0)),
42    );
43    println!("C: 30 (base) + A * 1.0");
44
45    println!("\n=== Attempting to resolve (should detect cycle) ===\n");
46
47    let context = StatContext::new();
48    match resolver.resolve(&a_id, &context) {
49        Err(StatError::CycleDetected(cycle)) => {
50            println!("✓ Cycle detected successfully!");
51            println!("\nCycle path:");
52            for (i, stat_id) in cycle.iter().enumerate() {
53                if i < cycle.len() - 1 {
54                    print!("{} -> ", stat_id);
55                } else {
56                    println!("{}", stat_id);
57                }
58            }
59        }
60        Err(e) => {
61            println!("✗ Unexpected error: {}", e);
62        }
63        Ok(_) => {
64            println!("✗ ERROR: Cycle was not detected! This should not happen.");
65        }
66    }
67
68    println!("\n=== Valid dependency chain (no cycle) ===\n");
69
70    // Reset resolver
71    let mut resolver2 = StatResolver::new();
72
73    let x_id = StatId::from_str("X");
74    let y_id = StatId::from_str("Y");
75    let z_id = StatId::from_str("Z");
76
77    // Linear chain: X -> Y -> Z (no cycle)
78    resolver2.register_source(x_id.clone(), Box::new(ConstantSource(10.0)));
79    println!("X: 10 (base)");
80
81    resolver2.register_source(y_id.clone(), Box::new(ConstantSource(20.0)));
82    resolver2.register_transform(
83        y_id.clone(),
84        Box::new(ScalingTransform::new(x_id.clone(), 1.0)),
85    );
86    println!("Y: 20 (base) + X * 1.0");
87
88    resolver2.register_source(z_id.clone(), Box::new(ConstantSource(30.0)));
89    resolver2.register_transform(
90        z_id.clone(),
91        Box::new(ScalingTransform::new(y_id.clone(), 1.0)),
92    );
93    println!("Z: 30 (base) + Y * 1.0");
94
95    println!("\n=== Resolving valid chain ===\n");
96
97    let results = resolver2.resolve_all(&context).unwrap();
98
99    println!("Results:");
100    println!("  X: {:.2}", results[&x_id].value);
101    println!("  Y: {:.2} (20 + 10)", results[&y_id].value);
102    println!("  Z: {:.2} (30 + 30)", results[&z_id].value);
103
104    println!("\n✓ Valid dependency chain resolved successfully!");
105}
examples/complex.rs (line 49)
13fn main() -> Result<(), StatError> {
14    let mut resolver = StatResolver::new();
15
16    // Define a character's stats
17    let str_id = StatId::from_str("STR");
18    let dex_id = StatId::from_str("DEX");
19    let int_id = StatId::from_str("INT");
20    let hp_id = StatId::from_str("HP");
21    let mp_id = StatId::from_str("MP");
22    let atk_id = StatId::from_str("ATK");
23    let crit_id = StatId::from_str("CRIT");
24    let dps_id = StatId::from_str("DPS");
25
26    println!("=== Character Stat System ===\n");
27
28    // Base attributes
29    println!("Base Attributes:");
30    resolver.register_source(str_id.clone(), Box::new(ConstantSource(20.0)));
31    println!("  STR: 20");
32
33    resolver.register_source(dex_id.clone(), Box::new(ConstantSource(15.0)));
34    println!("  DEX: 15");
35
36    resolver.register_source(int_id.clone(), Box::new(ConstantSource(25.0)));
37    println!("  INT: 25");
38
39    // HP: Base + STR scaling + item bonuses
40    println!("\nHP Calculation:");
41    resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
42    println!("  Base: 100");
43
44    resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
45    println!("  Item bonus: +50");
46
47    resolver.register_transform(
48        hp_id.clone(),
49        Box::new(ScalingTransform::new(str_id.clone(), 5.0)),
50    );
51    println!("  STR scaling: +STR * 5");
52
53    resolver.register_transform(hp_id.clone(), Box::new(MultiplicativeTransform::new(1.1)));
54    println!("  Passive: +10%");
55
56    // MP: Base + INT scaling
57    println!("\nMP Calculation:");
58    resolver.register_source(mp_id.clone(), Box::new(ConstantSource(50.0)));
59    println!("  Base: 50");
60
61    resolver.register_transform(
62        mp_id.clone(),
63        Box::new(ScalingTransform::new(int_id.clone(), 3.0)),
64    );
65    println!("  INT scaling: +INT * 3");
66
67    // ATK: Base + STR scaling + DEX scaling
68    println!("\nATK Calculation:");
69    resolver.register_source(atk_id.clone(), Box::new(ConstantSource(30.0)));
70    println!("  Base: 30");
71
72    resolver.register_transform(
73        atk_id.clone(),
74        Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
75    );
76    println!("  STR scaling: +STR * 2");
77
78    resolver.register_transform(
79        atk_id.clone(),
80        Box::new(ScalingTransform::new(dex_id.clone(), 1.0)),
81    );
82    println!("  DEX scaling: +DEX * 1");
83
84    resolver.register_transform(atk_id.clone(), Box::new(ClampTransform::new(0.0, 200.0)));
85    println!("  Clamp: [0, 200]");
86
87    // CRIT: Base + DEX scaling
88    println!("\nCRIT Calculation:");
89    resolver.register_source(crit_id.clone(), Box::new(ConstantSource(5.0)));
90    println!("  Base: 5");
91
92    resolver.register_transform(
93        crit_id.clone(),
94        Box::new(ScalingTransform::new(dex_id.clone(), 2.0)),
95    );
96    println!("  DEX scaling: +DEX * 2");
97
98    // DPS: Depends on ATK and CRIT
99    println!("\nDPS Calculation:");
100    resolver.register_source(dps_id.clone(), Box::new(ConstantSource(0.0)));
101    println!("  Base: 0");
102
103    resolver.register_transform(
104        dps_id.clone(),
105        Box::new(ScalingTransform::new(atk_id.clone(), 1.0)),
106    );
107    println!("  ATK contribution: +ATK * 1");
108
109    resolver.register_transform(
110        dps_id.clone(),
111        Box::new(ScalingTransform::new(crit_id.clone(), 0.5)),
112    );
113    println!("  CRIT contribution: +CRIT * 0.5");
114
115    let context = StatContext::new();
116
117    println!("\n=== Resolving All Stats ===\n");
118    let results = resolver.resolve_all(&context)?;
119
120    // Display final stats
121    println!("=== Final Character Stats ===\n");
122
123    let stats = vec![
124        ("STR", &str_id),
125        ("DEX", &dex_id),
126        ("INT", &int_id),
127        ("HP", &hp_id),
128        ("MP", &mp_id),
129        ("ATK", &atk_id),
130        ("CRIT", &crit_id),
131        ("DPS", &dps_id),
132    ];
133
134    for (name, id) in stats {
135        if let Some(resolved) = results.get(id) {
136            println!("{}: {:.2}", name, resolved.value);
137        }
138    }
139
140    println!("\n=== Detailed Breakdown ===\n");
141
142    // Show detailed breakdown for DPS
143    if let Some(dps) = results.get(&dps_id) {
144        println!("DPS Breakdown:");
145        println!("  Final Value: {:.2}", dps.value);
146
147        println!("\n  Sources:");
148        for (desc, value) in &dps.sources {
149            println!("    {}: {:.2}", desc, value);
150        }
151
152        println!("\n  Transforms:");
153        for (desc, value) in &dps.transforms {
154            println!("    {}: {:.2}", desc, value);
155        }
156    }
157
158    Ok(())
159}

Trait Implementations§

Source§

impl Clone for ScalingTransform

Source§

fn clone(&self) -> ScalingTransform

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ScalingTransform

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl StatTransform for ScalingTransform

Source§

fn depends_on(&self) -> Vec<StatId>

Get the list of stat IDs this transform depends on. Read more
Source§

fn apply( &self, input: f64, dependencies: &HashMap<StatId, f64>, _context: &StatContext, ) -> Result<f64, StatError>

Apply the transform to an input value. Read more
Source§

fn description(&self) -> String

Get a human-readable description of this transform. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.