big_brain/
scorers.rs

1//! Scorers look at the world and boil down arbitrary characteristics into a
2//! range of 0.0..=1.0. This module includes the ScorerBuilder trait and some
3//! built-in Composite Scorers.
4
5use std::{cmp::Ordering, sync::Arc};
6
7use bevy::prelude::*;
8#[cfg(feature = "trace")]
9use bevy::utils::tracing::trace;
10
11use crate::{
12    evaluators::Evaluator,
13    measures::{Measure, WeightedMeasure},
14    thinker::{Actor, Scorer, ScorerSpan},
15};
16
17/// Score value between `0.0..=1.0` associated with a Scorer.
18#[derive(Clone, Component, Debug, Default, Reflect)]
19pub struct Score(pub(crate) f32);
20
21impl Score {
22    /// Returns the `Score`'s current value.
23    pub fn get(&self) -> f32 {
24        self.0
25    }
26
27    /// Set the `Score`'s value.
28    ///
29    /// ### Panics
30    ///
31    /// Panics if `value` isn't within `0.0..=1.0`.
32    pub fn set(&mut self, value: f32) {
33        if !(0.0..=1.0).contains(&value) {
34            panic!("Score value must be between 0.0 and 1.0");
35        }
36        self.0 = value;
37    }
38
39    /// Set the `Score`'s value. Allows values outside the range `0.0..=1.0`
40    /// WARNING: `Scorer`s are significantly harder to compose when there
41    /// isn't a set scale. Avoid using unless it's not feasible to rescale
42    /// and use `set` instead.
43    pub fn set_unchecked(&mut self, value: f32) {
44        self.0 = value;
45    }
46}
47
48/// Trait that must be defined by types in order to be `ScorerBuilder`s.
49/// `ScorerBuilder`s' job is to spawn new `Scorer` entities. In general, most
50/// of this is already done for you, and the only method you really have to
51/// implement is `.build()`.
52///
53/// The `build()` method MUST be implemented for any `ScorerBuilder`s you want
54/// to define.
55#[reflect_trait]
56pub trait ScorerBuilder: std::fmt::Debug + Sync + Send {
57    /// MUST insert your concrete Scorer component into the Scorer [`Entity`],
58    ///  using `cmd`. You _may_ use `actor`, but it's perfectly normal to just
59    ///  ignore it.
60    ///
61    /// In most cases, your `ScorerBuilder` and `Scorer` can be the same type.
62    /// The only requirement is that your struct implements `Debug`,
63    /// `Component, `Clone`. You can then use the derive macro `ScorerBuilder`
64    /// to turn your struct into a `ScorerBuilder`
65    ///
66    /// ### Example
67    ///
68    /// Using the derive macro (the easy way):
69    ///
70    /// ```
71    /// # use bevy::prelude::*;
72    /// # use big_brain::prelude::*;
73    /// #[derive(Debug, Clone, Component, ScorerBuilder)]
74    /// #[scorer_label = "MyScorerLabel"] // Optional. Defaults to type name.
75    /// struct MyScorer;
76    /// ```
77    ///
78    /// Implementing it manually:
79    ///
80    /// ```
81    /// # use bevy::prelude::*;
82    /// # use big_brain::prelude::*;
83    /// #[derive(Debug)]
84    /// struct MyBuilder;
85    /// #[derive(Debug, Component)]
86    /// struct MyScorer;
87    ///
88    /// impl ScorerBuilder for MyBuilder {
89    ///   fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
90    ///     cmd.entity(scorer).insert(MyScorer);
91    ///   }
92    /// }
93    /// ```
94    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity);
95
96    /// A label to display when logging using the Scorer's tracing span.
97    fn label(&self) -> Option<&str> {
98        None
99    }
100}
101
102pub fn spawn_scorer<T: ScorerBuilder + ?Sized>(
103    builder: &T,
104    cmd: &mut Commands,
105    actor: Entity,
106) -> Entity {
107    let scorer_ent = cmd.spawn_empty().id();
108    let span = ScorerSpan::new(scorer_ent, ScorerBuilder::label(builder));
109    let _guard = span.span().enter();
110    debug!("New Scorer spawned.");
111    cmd.entity(scorer_ent)
112        .insert(Name::new("Scorer"))
113        .insert(Score::default())
114        .insert(Actor(actor));
115    builder.build(cmd, scorer_ent, actor);
116    std::mem::drop(_guard);
117    cmd.entity(scorer_ent).insert(span);
118    scorer_ent
119}
120
121/// Scorer that always returns the same, fixed score. Good for combining with
122/// things creatively!
123#[derive(Clone, Component, Debug, Reflect)]
124pub struct FixedScore(pub f32);
125
126impl FixedScore {
127    pub fn build(score: f32) -> FixedScorerBuilder {
128        FixedScorerBuilder { score, label: None }
129    }
130}
131
132pub fn fixed_score_system(mut query: Query<(&FixedScore, &mut Score, &ScorerSpan)>) {
133    for (FixedScore(fixed), mut score, _span) in query.iter_mut() {
134        #[cfg(feature = "trace")]
135        {
136            let _guard = _span.span().enter();
137            trace!("FixedScore: {}", fixed);
138        }
139        score.set(*fixed);
140    }
141}
142
143#[derive(Debug, Reflect)]
144pub struct FixedScorerBuilder {
145    score: f32,
146    label: Option<String>,
147}
148
149impl FixedScorerBuilder {
150    pub fn label(mut self, label: impl Into<String>) -> Self {
151        self.label = Some(label.into());
152        self
153    }
154}
155
156impl ScorerBuilder for FixedScorerBuilder {
157    fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
158        cmd.entity(scorer).insert(FixedScore(self.score));
159    }
160
161    fn label(&self) -> Option<&str> {
162        self.label.as_deref().or(Some("FixedScore"))
163    }
164}
165
166/// Composite Scorer that takes any number of other Scorers and returns the
167/// sum of their [`Score`] values if each _individual_ [`Score`] is at or
168/// above the configured `threshold`.
169///
170/// ### Example
171///
172/// ```
173/// # use bevy::prelude::*;
174/// # use big_brain::prelude::*;
175/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
176/// # struct MyScorer;
177/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
178/// # struct MyOtherScorer;
179/// # #[derive(Debug, Clone, Component, ActionBuilder)]
180/// # struct MyAction;
181/// # fn main() {
182/// Thinker::build()
183///     .when(
184///         AllOrNothing::build(0.8)
185///           .push(MyScorer)
186///           .push(MyOtherScorer),
187///         MyAction);
188/// # ;
189/// # }
190/// ```
191#[derive(Component, Debug, Reflect)]
192pub struct AllOrNothing {
193    threshold: f32,
194    scorers: Vec<Scorer>,
195}
196
197impl AllOrNothing {
198    pub fn build(threshold: f32) -> AllOrNothingBuilder {
199        AllOrNothingBuilder {
200            threshold,
201            scorers: Vec::new(),
202            scorer_labels: Vec::new(),
203            label: None,
204        }
205    }
206}
207
208pub fn all_or_nothing_system(
209    query: Query<(Entity, &AllOrNothing, &ScorerSpan)>,
210    mut scores: Query<&mut Score>,
211) {
212    for (
213        aon_ent,
214        AllOrNothing {
215            threshold,
216            scorers: children,
217        },
218        _span,
219    ) in query.iter()
220    {
221        let mut sum = 0.0;
222        for Scorer(child) in children.iter() {
223            let score = scores.get_mut(*child).expect("where is it?");
224            if score.0 < *threshold {
225                sum = 0.0;
226                break;
227            } else {
228                sum += score.0;
229            }
230        }
231        let mut score = scores.get_mut(aon_ent).expect("where did it go?");
232        score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
233        #[cfg(feature = "trace")]
234        {
235            let _guard = _span.span().enter();
236            trace!("AllOrNothing score: {}", score.get());
237        }
238    }
239}
240
241#[derive(Debug, Clone, Reflect)]
242pub struct AllOrNothingBuilder {
243    threshold: f32,
244    #[reflect(ignore)]
245    scorers: Vec<Arc<dyn ScorerBuilder>>,
246    scorer_labels: Vec<String>,
247    label: Option<String>,
248}
249
250impl AllOrNothingBuilder {
251    /// Add another Scorer to this [`ScorerBuilder`].
252    pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
253        if let Some(label) = scorer.label() {
254            self.scorer_labels.push(label.into());
255        } else {
256            self.scorer_labels.push("Unnamed Scorer".into());
257        }
258        self.scorers.push(Arc::new(scorer));
259        self
260    }
261
262    /// Set a label for this Action.
263    pub fn label(mut self, label: impl AsRef<str>) -> Self {
264        self.label = Some(label.as_ref().into());
265        self
266    }
267}
268
269impl ScorerBuilder for AllOrNothingBuilder {
270    fn label(&self) -> Option<&str> {
271        self.label.as_deref().or(Some("AllOrNothing"))
272    }
273
274    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
275        let scorers: Vec<_> = self
276            .scorers
277            .iter()
278            .map(|scorer| spawn_scorer(&**scorer, cmd, actor))
279            .collect();
280        cmd.entity(scorer)
281            .insert(Score::default())
282            .add_children(&scorers[..])
283            .insert(Name::new("Scorer"))
284            .insert(AllOrNothing {
285                threshold: self.threshold,
286                scorers: scorers.into_iter().map(Scorer).collect(),
287            });
288    }
289}
290
291/// 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`.
292///
293/// ### Example
294///
295/// ```
296/// # use bevy::prelude::*;
297/// # use big_brain::prelude::*;
298/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
299/// # struct MyScorer;
300/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
301/// # struct MyOtherScorer;
302/// # #[derive(Debug, Clone, Component, ActionBuilder)]
303/// # struct MyAction;
304/// # fn main() {
305/// Thinker::build()
306///     .when(
307///         SumOfScorers::build(0.8)
308///           .push(MyScorer)
309///           .push(MyOtherScorer),
310///         MyAction)
311/// # ;
312/// # }
313/// ```
314#[derive(Component, Debug, Reflect)]
315pub struct SumOfScorers {
316    threshold: f32,
317    scorers: Vec<Scorer>,
318    scorer_labels: Vec<String>,
319}
320
321impl SumOfScorers {
322    pub fn build(threshold: f32) -> SumOfScorersBuilder {
323        SumOfScorersBuilder {
324            threshold,
325            scorers: Vec::new(),
326            scorer_labels: Vec::new(),
327            label: None,
328        }
329    }
330}
331
332pub fn sum_of_scorers_system(
333    query: Query<(Entity, &SumOfScorers, &ScorerSpan)>,
334    mut scores: Query<&mut Score>,
335) {
336    for (
337        sos_ent,
338        SumOfScorers {
339            threshold,
340            scorers: children,
341            ..
342        },
343        _span,
344    ) in query.iter()
345    {
346        let mut sum = 0.0;
347        for Scorer(child) in children.iter() {
348            let score = scores.get_mut(*child).expect("where is it?");
349            sum += score.0;
350        }
351        if sum < *threshold {
352            sum = 0.0;
353        }
354        let mut score = scores.get_mut(sos_ent).expect("where did it go?");
355        score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
356        #[cfg(feature = "trace")]
357        {
358            let _guard = _span.span().enter();
359            trace!(
360                "SumOfScorers score: {}, from {} scores",
361                score.get(),
362                children.len()
363            );
364        }
365    }
366}
367
368#[derive(Debug, Clone, Reflect)]
369pub struct SumOfScorersBuilder {
370    threshold: f32,
371    #[reflect(ignore)]
372    scorers: Vec<Arc<dyn ScorerBuilder>>,
373    scorer_labels: Vec<String>,
374    label: Option<String>,
375}
376
377impl SumOfScorersBuilder {
378    /// Add a new Scorer to this [`SumOfScorersBuilder`].
379    pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
380        if let Some(label) = scorer.label() {
381            self.scorer_labels.push(label.into());
382        } else {
383            self.scorer_labels.push("Unnamed Scorer".into());
384        }
385        self.scorers.push(Arc::new(scorer));
386        self
387    }
388
389    /// Set a label for this Action.
390    pub fn label(mut self, label: impl AsRef<str>) -> Self {
391        self.label = Some(label.as_ref().into());
392        self
393    }
394}
395
396impl ScorerBuilder for SumOfScorersBuilder {
397    fn label(&self) -> Option<&str> {
398        self.label.as_deref().or(Some("SumOfScorers"))
399    }
400
401    #[allow(clippy::needless_collect)]
402    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
403        let scorers: Vec<_> = self
404            .scorers
405            .iter()
406            .map(|scorer| spawn_scorer(&**scorer, cmd, actor))
407            .collect();
408        cmd.entity(scorer)
409            .add_children(&scorers[..])
410            .insert(SumOfScorers {
411                threshold: self.threshold,
412                scorers: scorers.into_iter().map(Scorer).collect(),
413                scorer_labels: self.scorer_labels.clone(),
414            });
415    }
416}
417
418/// Composite Scorer that takes any number of other Scorers and returns the
419/// product of their [`Score`]. If the resulting score is less than the
420/// threshold, it returns 0.
421///
422/// The Scorer can also apply a compensation factor based on the number of
423/// Scores passed to it. This can be enabled by passing `true` to the
424/// `use_compensation` method on the builder.
425///
426/// ### Example
427///
428/// ```
429/// # use bevy::prelude::*;
430/// # use big_brain::prelude::*;
431/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
432/// # struct MyScorer;
433/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
434/// # struct MyOtherScorer;
435/// # #[derive(Debug, Clone, Component, ActionBuilder)]
436/// # struct MyAction;
437/// # fn main() {
438/// Thinker::build()
439///     .when(
440///         ProductOfScorers::build(0.5)
441///           .use_compensation(true)
442///           .push(MyScorer)
443///           .push(MyOtherScorer),
444///         MyAction)
445/// # ;
446/// # }
447/// ```
448
449#[derive(Component, Debug, Reflect)]
450pub struct ProductOfScorers {
451    threshold: f32,
452    use_compensation: bool,
453    scorers: Vec<Scorer>,
454    scorer_labels: Vec<String>,
455}
456
457impl ProductOfScorers {
458    pub fn build(threshold: f32) -> ProductOfScorersBuilder {
459        ProductOfScorersBuilder {
460            threshold,
461            use_compensation: false,
462            scorers: Vec::new(),
463            scorer_labels: Vec::new(),
464            label: None,
465        }
466    }
467}
468
469pub fn product_of_scorers_system(
470    query: Query<(Entity, &ProductOfScorers, &ScorerSpan)>,
471    mut scores: Query<&mut Score>,
472) {
473    for (
474        sos_ent,
475        ProductOfScorers {
476            threshold,
477            use_compensation,
478            scorers: children,
479            ..
480        },
481        _span,
482    ) in query.iter()
483    {
484        let mut product = 1.0;
485        let mut num_scorers = 0;
486
487        for Scorer(child) in children.iter() {
488            let score = scores.get_mut(*child).expect("where is it?");
489            product *= score.0;
490            num_scorers += 1;
491        }
492
493        // See for example
494        // http://www.gdcvault.com/play/1021848/Building-a-Better-Centaur-AI
495        if *use_compensation && product < 1.0 {
496            let mod_factor = 1.0 - 1.0 / (num_scorers as f32);
497            let makeup = (1.0 - product) * mod_factor;
498            product += makeup * product;
499        }
500
501        if product < *threshold {
502            product = 0.0;
503        }
504
505        let mut score = scores.get_mut(sos_ent).expect("where did it go?");
506        score.set(product.clamp(0.0, 1.0));
507        #[cfg(feature = "trace")]
508        {
509            let _guard = _span.span().enter();
510            trace!(
511                "ProductOfScorers score: {}, from {} scores",
512                score.get(),
513                children.len()
514            );
515        }
516    }
517}
518
519#[derive(Debug, Clone)]
520pub struct ProductOfScorersBuilder {
521    threshold: f32,
522    use_compensation: bool,
523    scorers: Vec<Arc<dyn ScorerBuilder>>,
524    scorer_labels: Vec<String>,
525    label: Option<String>,
526}
527
528impl ProductOfScorersBuilder {
529    /// To account for the fact that the total score will be reduced for
530    /// scores with more inputs, we can optionally apply a compensation factor
531    /// by calling this and passing `true`
532    pub fn use_compensation(mut self, use_compensation: bool) -> Self {
533        self.use_compensation = use_compensation;
534        self
535    }
536
537    /// Add a new scorer to this [`ProductOfScorersBuilder`].
538    pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
539        if let Some(label) = scorer.label() {
540            self.scorer_labels.push(label.into());
541        } else {
542            self.scorer_labels.push("Unnamed Scorer".into());
543        }
544        self.scorers.push(Arc::new(scorer));
545        self
546    }
547
548    /// Set a label for this Action.
549    pub fn label(mut self, label: impl AsRef<str>) -> Self {
550        self.label = Some(label.as_ref().into());
551        self
552    }
553}
554
555impl ScorerBuilder for ProductOfScorersBuilder {
556    fn label(&self) -> Option<&str> {
557        self.label.as_deref().or(Some("ProductOfScorers"))
558    }
559
560    #[allow(clippy::needless_collect)]
561    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
562        let scorers: Vec<_> = self
563            .scorers
564            .iter()
565            .map(|scorer| spawn_scorer(&**scorer, cmd, actor))
566            .collect();
567        cmd.entity(scorer)
568            .add_children(&scorers[..])
569            .insert(ProductOfScorers {
570                threshold: self.threshold,
571                use_compensation: self.use_compensation,
572                scorers: scorers.into_iter().map(Scorer).collect(),
573                scorer_labels: self.scorer_labels.clone(),
574            });
575    }
576}
577
578/// Composite Scorer that takes any number of other Scorers and returns the
579/// single highest value [`Score`] if  _any_ [`Score`]s are at or above the
580/// configured `threshold`.
581///
582/// ### Example
583///
584/// ```
585/// # use bevy::prelude::*;
586/// # use big_brain::prelude::*;
587/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
588/// # struct MyScorer;
589/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
590/// # struct MyOtherScorer;
591/// # #[derive(Debug, Clone, Component, ActionBuilder)]
592/// # struct MyAction;
593/// # fn main() {
594/// Thinker::build()
595///     .when(
596///         WinningScorer::build(0.8)
597///           .push(MyScorer)
598///           .push(MyOtherScorer),
599///         MyAction)
600/// # ;
601/// # }
602/// ```
603
604#[derive(Component, Debug, Reflect)]
605pub struct WinningScorer {
606    threshold: f32,
607    scorers: Vec<Scorer>,
608    scorer_labels: Vec<String>,
609}
610
611impl WinningScorer {
612    pub fn build(threshold: f32) -> WinningScorerBuilder {
613        WinningScorerBuilder {
614            threshold,
615            scorers: Vec::new(),
616            scorer_labels: Vec::new(),
617            label: None,
618        }
619    }
620}
621
622pub fn winning_scorer_system(
623    mut query: Query<(Entity, &mut WinningScorer, &ScorerSpan)>,
624    mut scores: Query<&mut Score>,
625) {
626    for (sos_ent, mut winning_scorer, _span) in query.iter_mut() {
627        let (threshold, children) = (winning_scorer.threshold, &mut winning_scorer.scorers);
628        let mut all_scores = children
629            .iter()
630            .map(|Scorer(e)| scores.get(*e).expect("where is it?"))
631            .collect::<Vec<&Score>>();
632
633        all_scores.sort_by(|a, b| a.get().partial_cmp(&b.get()).unwrap_or(Ordering::Equal));
634        let winning_score_or_zero = match all_scores.last() {
635            Some(s) => {
636                if s.get() < threshold {
637                    0.0
638                } else {
639                    s.get()
640                }
641            }
642            None => 0.0,
643        };
644        let mut score = scores.get_mut(sos_ent).expect("where did it go?");
645        score.set(crate::evaluators::clamp(winning_score_or_zero, 0.0, 1.0));
646        #[cfg(feature = "trace")]
647        {
648            let _guard = _span.span().enter();
649            trace!(
650                "WinningScorer score: {}, from {} scores",
651                score.get(),
652                children.len()
653            );
654        }
655    }
656}
657
658#[derive(Debug, Clone, Reflect)]
659pub struct WinningScorerBuilder {
660    threshold: f32,
661    #[reflect(ignore)]
662    scorers: Vec<Arc<dyn ScorerBuilder>>,
663    scorer_labels: Vec<String>,
664    label: Option<String>,
665}
666
667impl WinningScorerBuilder {
668    /// Add another Scorer to this [`WinningScorerBuilder`].
669    pub fn push(mut self, scorer: impl ScorerBuilder + 'static) -> Self {
670        if let Some(label) = scorer.label() {
671            self.scorer_labels.push(label.into());
672        } else {
673            self.scorer_labels.push("Unnamed Scorer".into())
674        }
675        self.scorers.push(Arc::new(scorer));
676        self
677    }
678
679    /// Set a label for this Action.
680    pub fn label(mut self, label: impl AsRef<str>) -> Self {
681        self.label = Some(label.as_ref().into());
682        self
683    }
684}
685
686impl ScorerBuilder for WinningScorerBuilder {
687    fn label(&self) -> Option<&str> {
688        self.label.as_deref().or(Some("WinningScorer"))
689    }
690
691    #[allow(clippy::needless_collect)]
692    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
693        let scorers: Vec<_> = self
694            .scorers
695            .iter()
696            .map(|scorer| spawn_scorer(&**scorer, cmd, actor))
697            .collect();
698        cmd.entity(scorer)
699            .add_children(&scorers[..])
700            .insert(WinningScorer {
701                threshold: self.threshold,
702                scorers: scorers.into_iter().map(Scorer).collect(),
703                scorer_labels: self.scorer_labels.clone(),
704            });
705    }
706}
707
708/// Composite scorer that takes a `ScorerBuilder` and applies an `Evaluator`.
709/// Note that unlike other composite scorers, `EvaluatingScorer` only takes
710/// one scorer upon building.
711///
712/// ### Example
713///
714/// ```
715/// # use bevy::prelude::*;
716/// # use big_brain::prelude::*;
717/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
718/// # struct MyScorer;
719/// # #[derive(Debug, Clone, Component, ActionBuilder)]
720/// # struct MyAction;
721/// # #[derive(Debug, Clone)]
722/// # struct MyEvaluator;
723/// # impl Evaluator for MyEvaluator {
724/// #    fn evaluate(&self, score: f32) -> f32 {
725/// #        score
726/// #    }
727/// # }
728/// # fn main() {
729/// Thinker::build()
730///     .when(
731///         EvaluatingScorer::build(MyScorer, MyEvaluator),
732///         MyAction)
733/// # ;
734/// # }
735/// ```
736#[derive(Component, Debug, Reflect)]
737#[reflect(from_reflect = false)]
738pub struct EvaluatingScorer {
739    scorer: Scorer,
740    evaluator_string: String,
741    #[reflect(ignore)]
742    evaluator: Arc<dyn Evaluator>,
743}
744
745impl EvaluatingScorer {
746    pub fn build(
747        scorer: impl ScorerBuilder + 'static,
748        evaluator: impl Evaluator + 'static,
749    ) -> EvaluatingScorerBuilder {
750        EvaluatingScorerBuilder {
751            scorer_label: scorer.label().map(|s| s.into()),
752            evaluator: Arc::new(evaluator),
753            scorer: Arc::new(scorer),
754            label: None,
755        }
756    }
757}
758
759pub fn evaluating_scorer_system(
760    query: Query<(Entity, &EvaluatingScorer, &ScorerSpan)>,
761    mut scores: Query<&mut Score>,
762) {
763    for (sos_ent, eval_scorer, _span) in query.iter() {
764        // Get the inner score
765        let inner_score = scores
766            .get(eval_scorer.scorer.0)
767            .expect("where did it go?")
768            .get();
769        // Get composite score
770        let mut score = scores.get_mut(sos_ent).expect("where did it go?");
771        score.set(crate::evaluators::clamp(
772            eval_scorer.evaluator.evaluate(inner_score),
773            0.0,
774            1.0,
775        ));
776        #[cfg(feature = "trace")]
777        {
778            let _guard = _span.span().enter();
779            trace!(
780                "EvaluatingScorer score: {}, from score: {}",
781                score.get(),
782                inner_score
783            );
784        }
785    }
786}
787
788#[derive(Debug, Reflect)]
789#[reflect(from_reflect = false)]
790pub struct EvaluatingScorerBuilder {
791    #[reflect(ignore)]
792    scorer: Arc<dyn ScorerBuilder>,
793    scorer_label: Option<String>,
794    #[reflect(ignore)]
795    evaluator: Arc<dyn Evaluator>,
796    label: Option<String>,
797}
798
799impl ScorerBuilder for EvaluatingScorerBuilder {
800    fn label(&self) -> Option<&str> {
801        self.label.as_deref().or(Some("EvaluatingScorer"))
802    }
803
804    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
805        let inner_scorer = spawn_scorer(&*self.scorer, cmd, actor);
806        let scorers = [inner_scorer];
807        cmd.entity(scorer)
808            .add_children(&scorers[..])
809            .insert(EvaluatingScorer {
810                evaluator: self.evaluator.clone(),
811                scorer: Scorer(inner_scorer),
812                evaluator_string: format!("{:#?}", self.evaluator),
813            });
814    }
815}
816
817/// Composite Scorer that allows more fine-grained control of how the scores
818/// are combined. The default is to apply a weighting
819///
820/// ### Example
821///
822/// Using the default measure ([`WeightedMeasure`]):
823///
824/// ```
825/// # use bevy::prelude::*;
826/// # use big_brain::prelude::*;
827/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
828/// # struct MyScorer;
829/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
830/// # struct MyOtherScorer;
831/// # #[derive(Debug, Clone, Component, ActionBuilder)]
832/// # struct MyAction;
833/// # fn main() {
834/// Thinker::build()
835///     .when(
836///         MeasuredScorer::build(0.5)
837///           .push(MyScorer, 0.9)
838///           .push(MyOtherScorer, 0.4),
839///         MyAction)
840/// # ;
841/// # }
842/// ```
843///
844/// Customising the measure:
845///
846/// ```
847/// # use bevy::prelude::*;
848/// # use big_brain::prelude::*;
849/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
850/// # struct MyScorer;
851/// # #[derive(Debug, Clone, Component, ScorerBuilder)]
852/// # struct MyOtherScorer;
853/// # #[derive(Debug, Clone, Component, ActionBuilder)]
854/// # struct MyAction;
855/// # fn main() {
856/// Thinker::build()
857///     .when(
858///         MeasuredScorer::build(0.5)
859///           .measure(ChebyshevDistance)
860///           .push(MyScorer, 0.8)
861///           .push(MyOtherScorer, 0.2),
862///         MyAction)
863/// # ;
864/// # }
865/// ```
866
867#[derive(Component, Debug, Reflect)]
868#[reflect(from_reflect = false)]
869pub struct MeasuredScorer {
870    threshold: f32,
871    #[reflect(ignore)]
872    measure: Arc<dyn Measure>,
873    measure_string: String,
874    scorers: Vec<(Scorer, f32)>,
875}
876
877impl MeasuredScorer {
878    pub fn build(threshold: f32) -> MeasuredScorerBuilder {
879        MeasuredScorerBuilder {
880            threshold,
881            measure: Arc::new(WeightedMeasure),
882            measure_string: format!("{WeightedMeasure:#?}"),
883            scorers: Vec::new(),
884            scorer_labels: Vec::new(),
885            label: None,
886        }
887    }
888}
889
890pub fn measured_scorers_system(
891    query: Query<(Entity, &MeasuredScorer, &ScorerSpan)>,
892    mut scores: Query<&mut Score>,
893) {
894    for (
895        sos_ent,
896        MeasuredScorer {
897            threshold,
898            measure,
899            scorers: children,
900            ..
901        },
902        _span,
903    ) in query.iter()
904    {
905        let measured_score = measure.calculate(
906            children
907                .iter()
908                .map(|(scorer, weight)| (scores.get(scorer.0).expect("where is it?"), *weight))
909                .collect::<Vec<_>>(),
910        );
911        let mut score = scores.get_mut(sos_ent).expect("where did it go?");
912
913        if measured_score < *threshold {
914            score.set(0.0);
915        } else {
916            score.set(measured_score.clamp(0.0, 1.0));
917        }
918        #[cfg(feature = "trace")]
919        {
920            let _guard = _span.span().enter();
921            trace!(
922                "MeasuredScorer score: {}, from {} scores",
923                score.get(),
924                children.len()
925            );
926        }
927    }
928}
929
930#[derive(Debug, Reflect)]
931#[reflect(from_reflect = false)]
932pub struct MeasuredScorerBuilder {
933    threshold: f32,
934    #[reflect(ignore)]
935    measure: Arc<dyn Measure>,
936    measure_string: String,
937    #[reflect(ignore)]
938    scorers: Vec<(Arc<dyn ScorerBuilder>, f32)>,
939    scorer_labels: Vec<String>,
940    label: Option<String>,
941}
942
943impl MeasuredScorerBuilder {
944    /// Sets the measure to be used to combine the child scorers
945    pub fn measure(mut self, measure: impl Measure + 'static) -> Self {
946        self.measure_string = format!("{measure:#?}");
947        self.measure = Arc::new(measure);
948        self
949    }
950
951    pub fn push(mut self, scorer: impl ScorerBuilder + 'static, weight: f32) -> Self {
952        if let Some(label) = scorer.label() {
953            self.scorer_labels.push(label.into());
954        } else {
955            self.scorer_labels.push("Unnamed Scorer".into());
956        }
957        self.scorers.push((Arc::new(scorer), weight));
958        self
959    }
960
961    /// Set a label for this ScorerBuilder.
962    pub fn label(mut self, label: impl AsRef<str>) -> Self {
963        self.label = Some(label.as_ref().into());
964        self
965    }
966}
967
968impl ScorerBuilder for MeasuredScorerBuilder {
969    fn label(&self) -> Option<&str> {
970        self.label.as_deref().or(Some("MeasuredScorer"))
971    }
972
973    #[allow(clippy::needless_collect)]
974    fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
975        let scorers: Vec<_> = self
976            .scorers
977            .iter()
978            .map(|(scorer, _)| spawn_scorer(&**scorer, cmd, actor))
979            .collect();
980        cmd.entity(scorer)
981            .add_children(&scorers[..])
982            .insert(MeasuredScorer {
983                threshold: self.threshold,
984                measure: self.measure.clone(),
985                scorers: scorers
986                    .into_iter()
987                    .map(Scorer)
988                    .zip(self.scorers.iter().map(|(_, weight)| *weight))
989                    .collect(),
990                measure_string: self.measure_string.clone(),
991            });
992    }
993}