Newer
Older
Scorers look at the world and boil down arbitrary characteristics into a range of 0.0..=1.0. This module includes the ScorerBuilder trait and some built-in Composite Scorers.
use std::{cmp::Ordering, sync::Arc};
use crate::{
evaluators::Evaluator,
thinker::{Actor, ScorerEnt},
};
Score value between `0.0..=1.0` associated with a Scorer.
#[derive(Clone, Component, Debug, Default)]
pub struct Score(pub(crate) f32);
impl Score {
/**
Returns the `Score`'s current value.
*/
/**
Set the `Score`'s value.
### Panics
Panics if `value` isn't within `0.0..=1.0`.
pub fn set(&mut self, value: f32) {
if !(0.0..=1.0).contains(&value) {
panic!("Score value must be between 0.0 and 1.0");
}
self.0 = value;
}
}
/**
Trait that must be defined by types in order to be `ScorerBuilder`s. `ScorerBuilder`s' job is to spawn new `Scorer` entities. In general, most of this is already done for you, and the only method you really have to implement is `.build()`.
The `build()` method MUST be implemented for any `ScorerBuilder`s you want to define.
*/
pub trait ScorerBuilder: std::fmt::Debug + Sync + Send {
/**
MUST insert your concrete Scorer component into the Scorer [`Entity`], using `cmd`. You _may_ use `actor`, but it's perfectly normal to just ignore it.
### Example
```no_run
struct MyBuilder;
struct MyScorer;
impl ScorerBuilder for MyBuilder {
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
cmd.entity(action).insert(MyScorer);
}
}
```
*/
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity);
/**
Don't implement this yourself unless you know what you're doing.
*/
fn attach(&self, cmd: &mut Commands, actor: Entity) -> Entity {
let scorer_ent = cmd.spawn().id();
.insert(Score::default())
.insert(Actor(actor));
self.build(cmd, scorer_ent, actor);
scorer_ent
}
/**
Scorer that always returns the same, fixed score. Good for combining with things creatively!
*/
impl FixedScore {
pub fn build(score: f32) -> FixedScoreBuilder {
FixedScoreBuilder(score)
}
}
pub fn fixed_score_system(mut query: Query<(&FixedScore, &mut Score)>) {
for (FixedScore(fixed), mut score) in query.iter_mut() {
score.set(*fixed);
}
}
impl ScorerBuilder for FixedScoreBuilder {
fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
cmd.entity(action).insert(FixedScore(self.0));
/**
Composite Scorer that takes any number of other Scorers and returns the sum of their [`Score`] values if each _individual_ [`Score`] is at or above the configured `threshold`.
### Example
```ignore
Thinker::build()
.when(
AllOrNothing::build()
.push(MyScorer)
.push(MyOtherScorer),
MyAction::build());
```
*/
pub struct AllOrNothing {
threshold: f32,
scorers: Vec<ScorerEnt>,
}
impl AllOrNothing {
pub fn build(threshold: f32) -> AllOrNothingBuilder {
AllOrNothingBuilder {
threshold,
scorers: Vec::new(),
}
}
}
pub fn all_or_nothing_system(query: Query<(Entity, &AllOrNothing)>, mut scores: Query<&mut Score>) {
for (
aon_ent,
AllOrNothing {
threshold,
scorers: children,
},
) in query.iter()
{
let mut sum = 0.0;
for ScorerEnt(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
if score.0 < *threshold {
sum = 0.0;
break;
} else {
sum += score.0;
}
}
let mut score = scores.get_mut(aon_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[derive(Debug, Clone)]
pub struct AllOrNothingBuilder {
threshold: f32,
scorers: Vec<Arc<dyn ScorerBuilder>>,
}
/**
Add another Scorer to this [`ScorerBuilder`].
*/

Kat Marchán
committed
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
self.scorers.push(Arc::new(scorer));
self
impl ScorerBuilder for AllOrNothingBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| scorer.attach(cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Score::default())
threshold: self.threshold,
scorers: scorers.into_iter().map(ScorerEnt).collect(),
});
/**
Composite Scorer that takes any number of other Scorers and returns the sum of their [`Score`] values if the _total_ summed [`Score`] is at or above the configured `threshold`.
### Example
```ignore
Thinker::build()
.when(
SumOfScorers::build()
.push(MyScorer)
.push(MyOtherScorer),
MyAction::build());
```
*/
pub struct SumOfScorers {
threshold: f32,
scorers: Vec<ScorerEnt>,
}
impl SumOfScorers {
pub fn build(threshold: f32) -> SumOfScorersBuilder {
SumOfScorersBuilder {
threshold,
scorers: Vec::new(),
}
}
}
pub fn sum_of_scorers_system(query: Query<(Entity, &SumOfScorers)>, mut scores: Query<&mut Score>) {
for (
sos_ent,
SumOfScorers {
threshold,
scorers: children,
},
) in query.iter()
{
let mut sum = 0.0;
for ScorerEnt(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
sum += score.0;
}
if sum < *threshold {
sum = 0.0;
}
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[derive(Debug, Clone)]
pub struct SumOfScorersBuilder {
threshold: f32,
scorers: Vec<Arc<dyn ScorerBuilder>>,
}

Kat Marchán
committed
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
self.scorers.push(Arc::new(scorer));
self
impl ScorerBuilder for SumOfScorersBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| scorer.attach(cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Transform::default())
.insert(GlobalTransform::default())

alexander
committed
.insert(SumOfScorers {
threshold: self.threshold,
scorers: scorers.into_iter().map(ScorerEnt).collect(),
});
/**
Composite Scorer that takes any number of other Scorers and returns the single highest value [`Score`] if _any_ [`Score`]s are at or above the configured `threshold`.
### Example
```ignore
Thinker::build()
.when(
WinningScorer::build()
.push(MyScorer)
.push(MyOtherScorer),
MyAction::build());
```
*/
pub struct WinningScorer {
threshold: f32,
scorers: Vec<ScorerEnt>,
}
impl WinningScorer {
pub fn build(threshold: f32) -> WinningScorerBuilder {
WinningScorerBuilder {
threshold,
scorers: Vec::new(),
}
}
}
pub fn winning_scorer_system(
mut query: Query<(Entity, &mut WinningScorer)>,
) {
for (sos_ent, mut winning_scorer) in query.iter_mut() {
let (threshold, children) = (winning_scorer.threshold, &mut winning_scorer.scorers);
let mut all_scores = children
.iter()
.map(|ScorerEnt(e)| scores.get(*e).expect("where is it?"))
.collect::<Vec<&Score>>();
all_scores.sort_by(|a, b| a.get().partial_cmp(&b.get()).unwrap_or(Ordering::Equal));
let winning_score_or_zero = match all_scores.last() {
Some(s) => {
if s.get() < threshold {
0.0
} else {
s.get()
}
}
None => 0.0,
};
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(winning_score_or_zero, 0.0, 1.0));
}
}
#[derive(Debug, Clone)]
pub struct WinningScorerBuilder {
threshold: f32,
scorers: Vec<Arc<dyn ScorerBuilder>>,
}
impl WinningScorerBuilder {
/**
Add another Scorer to this [`ScorerBuilder`].
*/

Kat Marchán
committed
pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
self.scorers.push(Arc::new(scorer));
self
}
}
impl ScorerBuilder for WinningScorerBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| scorer.attach(cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Transform::default())
.insert(GlobalTransform::default())
.insert(WinningScorer {
threshold: self.threshold,
scorers: scorers.into_iter().map(ScorerEnt).collect(),
});
}
}
/**
Composite scorer that takes a `ScorerBuilder` and applies an `Evaluator`. Note that
unlike other composite scorers, `EvaluatingScorer` only takes one scorer upon building.
### Example
```ignore
Thinker::build()
.when(
EvaluatingScorer::build(MyScorer, MyEvaluator),
MyAction::build());
```
*/
pub struct EvaluatingScorer {
scorer: ScorerEnt,
evaluator: Arc<dyn Evaluator>,
}
impl EvaluatingScorer {
pub fn build(
scorer: impl ScorerBuilder + 'static,
evaluator: impl Evaluator + 'static,
) -> EvaluatingScorerBuilder {
EvaluatingScorerBuilder {
evaluator: Arc::new(evaluator),
scorer: Arc::new(scorer),
}
}
}
pub fn evaluating_scorer_system(
query: Query<(Entity, &EvaluatingScorer)>,
) {
for (sos_ent, eval_scorer) in query.iter() {
// Get the inner score
let inner_score = scores
.get(eval_scorer.scorer.0)
.expect("where did it go?")
.get();
// Get composite score
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
score.set(crate::evaluators::clamp(
eval_scorer.evaluator.evaluate(inner_score),
0.0,
1.0,
));
}
}
#[derive(Debug, Clone)]
pub struct EvaluatingScorerBuilder {
pub scorer: Arc<dyn ScorerBuilder>,
pub evaluator: Arc<dyn Evaluator>,
}
impl ScorerBuilder for EvaluatingScorerBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let inner_scorer = self.scorer.attach(cmd, actor);
cmd.entity(scorer)
.insert(Transform::default())
.insert(GlobalTransform::default())
.insert(EvaluatingScorer {
evaluator: self.evaluator.clone(),
scorer: ScorerEnt(inner_scorer),
});
}
}