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.
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);
}
}
#[derive(Debug, Clone)]
pub struct FixedScoreBuilder(f32);
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());
```
*/
#[derive(Debug)]
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());
```
*/
#[derive(Debug)]
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(),
});
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/**
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());
```
*/
#[derive(Debug)]
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)>,
mut scores: QuerySet<(Query<&Score>, Query<&mut Score>)>,
) {
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.q0().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.q1_mut().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(),
});
}
}
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
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
435
/**
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());
```
*/
#[derive(Debug, Clone)]
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)>,
mut scores: QuerySet<(Query<&Score>, Query<&mut Score>)>,
) {
for (sos_ent, eval_scorer) in query.iter() {
// Get the inner score
let inner_score = scores
.q0()
.get(eval_scorer.scorer.0)
.expect("where did it go?")
.get();
// Get composite score
let mut score = scores.q1_mut().get_mut(sos_ent).expect("where did it go?");
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),
});
}
}