idanarye / bevy-yoetz

Apache License 2.0
8 stars 2 forks source link

Adding curve helpers. #4

Open seivan opened 1 month ago

seivan commented 1 month ago

I was thinking of normalizing scores so I can start using Curves. I started of with this. I was wondering if it should be included. I am not saying to change the suggestion score argument, but this could be useful. Missing some curves, but the idea was to have plain english naming convention.

#[derive(Debug)]
enum ScoreCurve {
    Increasing(CurveRate),
    Decreasing(CurveRate),
    Fluctuating(CurveRate),
    Step,
}

#[derive(Debug)]
enum CurveRate {
    Linear,
    FastAtFirstSlowLater,
    SlowAtFirstFastLater,
    SlowAtEitherEndFastInMiddle,
    FastAtEitherEndSlowInMiddle,
    MoreDynamic,
}

struct Score {
    raw_value: f32,
    curve: ScoreCurve,
}

impl Score {
    fn normalized_value(&self) -> f32 {
        if self.raw_value >= 0.0 && self.raw_value <= 1.0 {
            self.raw_value
        } else if self.raw_value > 1.0 && self.raw_value <= 10.0 {
            self.raw_value / 10.0
        } else if self.raw_value > 10.0 && self.raw_value <= 100.0 {
            self.raw_value / 100.0
        } else if self.raw_value > 100.0 && self.raw_value <= 1000.0 {
            self.raw_value / 1000.0
        } else {
            let abs_value = self.raw_value.abs();
            let base = 10_f32.powi(abs_value.log10().ceil() as i32);
            self.raw_value / base
        }
    }

    fn curved_score(&self) -> f32 {
        let normalized = self.normalized_value();
       //... matching cases and return 

    }
}

// Example usage
fn main() {
    let scores = vec![
        Score { raw_value: 0.2, curve: ScoreCurve::Increasing(CurveRate::FastAtFirstSlowLater) },
        Score { raw_value: 0.6, curve: ScoreCurve::Increasing(CurveRate::FastAtFirstSlowLater) },
        Score { raw_value: 0.2, curve: ScoreCurve::Increasing(CurveRate::SlowAtFirstFastLater) },
        Score { raw_value: 0.6, curve: ScoreCurve::Increasing(CurveRate::SlowAtFirstFastLater) },
        Score { raw_value: 0.2, curve: ScoreCurve::Increasing(CurveRate::SlowAtEitherEndFastInMiddle) },
        Score { raw_value: 0.6, curve: ScoreCurve::Increasing(CurveRate::SlowAtEitherEndFastInMiddle) },
        Score { raw_value: 0.2, curve: ScoreCurve::Increasing(CurveRate::FastAtEitherEndSlowInMiddle) },
        Score { raw_value: 0.6, curve: ScoreCurve::Increasing(CurveRate::FastAtEitherEndSlowInMiddle) },
        Score { raw_value: 0.2, curve: ScoreCurve::Decreasing(CurveRate::FastAtFirstSlowLater) },
        Score { raw_value: 0.9, curve: ScoreCurve::Decreasing(CurveRate::FastAtFirstSlowLater) },
    ];

    for (i, score) in scores.iter().enumerate() {
        println!("Curved Score {}: {}", i + 1, score.curved_score());
    }
}

Reading

idanarye commented 1 month ago

Not sure what the curve is supposed to do exactly. Scores are computed "from scratch" on every frame (with the exception of the consistency_bonus) , but a curve implies some kind of continuity - some other parameter that affects the curve.

How will these curves affect how Yoetz chooses the strategy?

idanarye commented 1 month ago

Also - Bevy has some builtin curve types - CubicCurve and RationalCurve. Any reason not to use one of them?

seivan commented 1 month ago

Not sure what the curve is supposed to do exactly. Scores are computed "from scratch" on every frame (with the exception of the consistency_bonus) , but a curve implies some kind of continuity - some other parameter that affects the curve.

No continuity, just logic to alter strengths of suggestions based on data.

How will these curves affect how Yoetz chooses the strategy?

It's wont. Yoetz work with the numbers it gets. That's why I am wondering if it should be included. This could very much be outside, but it would make help to make Yeotz a little more batteries included.

The reasoning for it is simple:

How much health should an NPC lose before it goes for retreat or medpack? These are two different suggestions but they might both use the same values, e.g health, number of enemies etc. Obviously you can write the code yourself but again, that's boiler plate, especially for a utility AI scoring library.

Say we're using health %. Meaning the lower the number is the more critical it is. So we want it to be Decreasing.

But not all numbers are the same, that's where curve comes in. Having 80% health is not so bad, but having 20% health is critical. So we want something like. SlowAtFirstFastLater This mean the lower the health gets, the more critical the suggestion becomes.

Also - Bevy has some builtin curve types - CubicCurve and RationalCurve. Any reason not to use one of them?

Naming. I didn't name them Polynomial or other therms. Should be in plain English.

Could use the Bevy ones internally for the actual calculation. I wrote my own for now, I suspected Bevy had them which is why I didn't add them.

fn curved_score(&self) -> f32 {
        let normalized = self.normalized_value();
       //... here, as long as we get a number out. 

    }
seivan commented 1 month ago

The API should really be Increasing(SlowAtEitherEndFastInMiddle)::score(f32) or something like that and not the monstrosity I showed here.

idanarye commented 1 month ago

So basically it's utility types/functions that spits regular numbers? And they could have been in a separate, non-Bevy crate - but you think they should be included in Yoetz anyway as helpers for calculating the scores?

seivan commented 1 month ago

So basically it's utility types/functions that spits regular numbers? And they could have been in a separate, non-Bevy crate - but you think they should be included in Yoetz anyway as helpers for calculating the scores?

That's your call. I'm a bit peeved that rust libraries split their crates. I rather have one crate doing the related field rather than have it be split up. If it's a build time concept, I feel like that's a problem upstream with the compiler.

Slightly off topic tangent.

I think if you're making a Utility AI library then you should include fuzzy logic as they are related. https://www.youtube.com/watch?v=TCf1GdRrerw

They go hand in hand.

However, if not I'll make the crate myself, that's not an issue. As long as Yoetz keeps accepting f32 input scores.