Skip to content
Snippets Groups Projects
Unverified Commit e54e8d37 authored by Kat Marchán's avatar Kat Marchán
Browse files

removed measures and weights to simplify things a bit

Fixes: https://github.com/zkat/big-brain/issues/5
parent c8b53507
No related branches found
No related tags found
No related merge requests found
...@@ -3,7 +3,7 @@ use proc_macro2::TokenStream; ...@@ -3,7 +3,7 @@ use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
#[derive(Debug, FromDeriveInput)] #[derive(Debug, FromDeriveInput)]
#[darling(attributes(action), supports(struct_named))] #[darling(attributes(action))]
pub struct Action { pub struct Action {
ident: syn::Ident, ident: syn::Ident,
generics: syn::Generics, generics: syn::Generics,
......
( (
// We have to use these special "externally tagged" specifiers instead of,
// say, `FirstToScore(threshold: 80.0)` due to a bug in RON itself:
// https://github.com/ron-rs/ron/issues/123
picker: {"FirstToScore": (threshold: 80.0)}, picker: {"FirstToScore": (threshold: 80.0)},
choices: [( choices: [(
consider: [{"Bladder": (weight: 3.0)}], when: {"Bladder": ()},
// Thinkers happen to also be actions, so you can nest theem!
then: {"Thinker": ( then: {"Thinker": (
picker: {"FirstToScore": (threshold: 80.0)}, picker: {"FirstToScore": (threshold: 80.0)},
choices: [( choices: [(
consider: [{"Bladder": (weight: 3.0)}], when: [{"Bladder": ()}],
then: {"Pee": ()} then: {"Pee": ()}
)] )]
)} )}
), ( ), (
consider: [{"Thirst": (weight: 2.0)}], when: {"Thirst": ()},
then: {"Drink": ()}, then: {"Drink": ()},
), ( ), (
consider: [{"Hunger": (weight: 2.0)}], when: {"Hunger": ()},
then: {"Eat": ()}, then: {"Eat": ()},
)], )],
otherwise: Some({"Meander": ()}), otherwise: Some({"Meander": ()}),
......
use bevy::prelude::*; use bevy::prelude::*;
use big_brain::{evaluators::*, *}; use big_brain::*;
// First, we define a "Thirst" component and associated system. This is NOT // First, we define a "Thirst" component and associated system. This is NOT
// THE AI. It's a plain old system that just makes an entity "thirstier" over // THE AI. It's a plain old system that just makes an entity "thirstier" over
...@@ -21,6 +21,7 @@ impl Thirst { ...@@ -21,6 +21,7 @@ impl Thirst {
pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) { pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
for mut thirst in thirsts.iter_mut() { for mut thirst in thirsts.iter_mut() {
thirst.thirst += thirst.per_second * (time.delta().as_micros() as f32 / 1000000.0); thirst.thirst += thirst.per_second * (time.delta().as_micros() as f32 / 1000000.0);
println!("Thirst: {}", thirst.thirst);
} }
} }
...@@ -33,7 +34,7 @@ pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) { ...@@ -33,7 +34,7 @@ pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
// These actions will be spawned and queued by the game engine when their // These actions will be spawned and queued by the game engine when their
// conditions trigger (we'll configure what these are later). // conditions trigger (we'll configure what these are later).
#[derive(Debug, Action)] #[derive(Debug, Action)]
pub struct DrinkAction {} pub struct DrinkAction;
// Associated with that DrinkAction, you then need to have a system that will // Associated with that DrinkAction, you then need to have a system that will
// actually execute those actions when they're "spawned" by the Big Brain // actually execute those actions when they're "spawned" by the Big Brain
...@@ -52,7 +53,7 @@ fn drink_action_system( ...@@ -52,7 +53,7 @@ fn drink_action_system(
// is in. You can think of Actions as state machines. They get requested, // is in. You can think of Actions as state machines. They get requested,
// they can be cancelled, they can run to completion, etc. Cancellations // they can be cancelled, they can run to completion, etc. Cancellations
// usually happen because the target action changed (due to a different // usually happen because the target action changed (due to a different
// Consideration winning). But you can also cancel the actions yourself by // Scorer winning). But you can also cancel the actions yourself by
// setting the state in the Action system. // setting the state in the Action system.
mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>, mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
) { ) {
...@@ -83,36 +84,22 @@ fn drink_action_system( ...@@ -83,36 +84,22 @@ fn drink_action_system(
// in the docs (later), but for now, just put them in there and trust the // in the docs (later), but for now, just put them in there and trust the
// system. :) // system. :)
#[derive(Debug, Scorer)] #[derive(Debug, Scorer)]
pub struct ScoreThirst { pub struct ScoreThirst;
#[scorer(default)]
pub evaluator: PowerEvaluator,
#[scorer(param)]
pub weight: f32,
}
// Look familiar? Similar dance to Actions here. // Look familiar? Similar dance to Actions here.
pub fn score_thirst_system( pub fn score_thirst_system(
thirsts: Query<&Thirst>, thirsts: Query<&Thirst>,
// Same dance with the Parent here, but now we've added a Utility! // Same dance with the Parent here, but now we've added a Utility!
mut query: Query<(&Parent, &ScoreThirst, &mut Score)>, mut query: Query<(&Parent, &mut Score), With<ScoreThirst>>,
) { ) {
for (Parent(actor), scorer, mut score) in query.iter_mut() { for (Parent(actor), mut score) in query.iter_mut() {
if let Ok(thirst) = thirsts.get(*actor) { if let Ok(thirst) = thirsts.get(*actor) {
// This is really what the job of a Consideration is. To calculate // This is really what the job of a Scorer is. To calculate
// a generic Utility value that the Big Brain engine will compare // a generic Utility value that the Big Brain engine will compare
// against others, over time, and use to make decisions. This is // against others, over time, and use to make decisions. This is
// generally "the higher the better", and "first across the finish // generally "the higher the better", and "first across the finish
// line", but that's all configurable! You can customize that to // line", but that's all configurable using Pickers!
// your heart's extent using Measures and Pickers. *score = Score(thirst.thirst);
//
// A note: You don't actually *need* evaluators/weights. You can
// literally just use linear values here and set thresholds
// accordingly. The evaluator is just there to give the value a
// bit of a curve.
*score = Score {
value: scorer.evaluator.evaluate(thirst.thirst),
weight: scorer.weight,
};
} }
} }
} }
...@@ -123,15 +110,15 @@ pub fn score_thirst_system( ...@@ -123,15 +110,15 @@ pub fn score_thirst_system(
// Thinkers are configured using RON right now, with a DSL that makes it easy // Thinkers are configured using RON right now, with a DSL that makes it easy
// to define, in data, the actual behavior you want. // to define, in data, the actual behavior you want.
pub fn init_entities(mut cmd: Commands) { pub fn init_entities(mut cmd: Commands) {
let actor = cmd.spawn().insert(Thirst::new(80.0, 2.0)).id(); let actor = cmd.spawn().insert(Thirst::new(70.0, 2.0)).id();
// Here's a very simple one that only has one consideration and one // Here's a very simple one that only has one scorer and one
// associated action. But you can have more of them, and even nest them by // associated action. But you can have more of them, and even nest them by
// using more Thinkers (which are actually themselves Actions). See // using more Thinkers (which are actually themselves Actions). See
// basic.ron in examples/ for a more involved Thinker definition. // basic.ron in examples/ for a more involved Thinker definition.
// //
// Ultimately, these Thinkers are meant to be usable by non-programmers: // Ultimately, these Thinkers are meant to be usable by non-programmers:
// You, the developer, create Actions and Considerations, and someone else // You, the developer, create Actions and Scorers, and someone else
// is then able to put them all together like LEGOs into all sorts of // is then able to put them all together like LEGOs into all sorts of
// intricate logic. // intricate logic.
Thinker::load_from_str( Thinker::load_from_str(
...@@ -139,7 +126,8 @@ pub fn init_entities(mut cmd: Commands) { ...@@ -139,7 +126,8 @@ pub fn init_entities(mut cmd: Commands) {
( (
picker: {"FirstToScore": (threshold: 80.0)}, picker: {"FirstToScore": (threshold: 80.0)},
choices: [( choices: [(
consider: [{"ThirstConsideration": (weight: 2.0)}], when: {"ScoreThirst": ()},
// This action will fire when (and as long as) ScoreThirst scores >=80.0.
then: {"DrinkAction": ()}, then: {"DrinkAction": ()},
)], )],
) )
......
...@@ -4,47 +4,31 @@ use serde::Deserialize; ...@@ -4,47 +4,31 @@ use serde::Deserialize;
use crate::{ use crate::{
actions::{Action, ActionState}, actions::{Action, ActionState},
scorers::{Scorer, Score}, scorers::{Scorer, Score},
measures::{Measure, WeightedMeasure},
thinker::{ActionEnt, ScorerEnt}, thinker::{ActionEnt, ScorerEnt},
}; };
// Contains different types of Considerations and Actions // Contains different types of Considerations and Actions
#[derive(Debug)] #[derive(Debug)]
pub struct Choice { pub struct Choice {
pub measure: Box<dyn Measure>, pub scorer: ScorerEnt,
pub utilities: Vec<ScorerEnt>,
pub action_state: ActionEnt, pub action_state: ActionEnt,
} }
impl Choice { impl Choice {
pub fn calculate(&self, utilities: &Query<&Score>) -> f32 { pub fn calculate(&self, scores: &Query<&Score>) -> f32 {
self.measure.calculate( scores.get(self.scorer.0).expect("Where did the score go?").0
self.utilities
.iter()
.map(|choice_cons| {
utilities
.get(choice_cons.0)
.expect("Where did the utility go?")
})
.collect(),
)
} }
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ChoiceBuilder { pub struct ChoiceBuilder {
pub consider: Vec<Box<dyn Scorer>>, pub when: Box<dyn Scorer>,
pub then: Box<dyn Action>, pub then: Box<dyn Action>,
} }
impl ChoiceBuilder { impl ChoiceBuilder {
pub fn build(self, actor: Entity, cmd: &mut Commands) -> Choice { pub fn build(self, actor: Entity, cmd: &mut Commands) -> Choice {
let action = self.then; let action = self.then;
Choice { Choice {
measure: Box::new(WeightedMeasure), scorer: self.when.build(actor, cmd),
utilities: self
.consider
.iter()
.map(|cons| cons.build(actor, cmd))
.collect(),
action_state: ActionState::build(action, actor, cmd), action_state: ActionState::build(action, actor, cmd),
} }
} }
......
...@@ -9,7 +9,6 @@ pub use scorers::*; ...@@ -9,7 +9,6 @@ pub use scorers::*;
pub use thinker::*; pub use thinker::*;
pub mod evaluators; pub mod evaluators;
pub mod measures;
pub mod pickers; pub mod pickers;
mod actions; mod actions;
......
use serde::{Deserialize, Serialize};
use crate::Score;
#[typetag::serde]
pub trait Measure: std::fmt::Debug + Sync + Send {
fn calculate(&self, utilities: Vec<&Score>) -> f32;
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WeightedMeasure;
#[typetag::serde]
impl Measure for WeightedMeasure {
fn calculate(&self, utilities: Vec<&Score>) -> f32 {
let wsum: f32 = utilities.iter().map(|el| el.weight).sum();
if wsum == 0.0 {
0.0
} else {
utilities
.iter()
.map(|el| el.weight / wsum * el.value.powf(2.0))
.sum::<f32>()
.powf(1.0 / 2.0)
}
}
}
...@@ -3,13 +3,10 @@ use bevy::prelude::*; ...@@ -3,13 +3,10 @@ use bevy::prelude::*;
use crate::ScorerEnt; use crate::ScorerEnt;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Score { pub struct Score(pub f32);
pub value: f32,
pub weight: f32,
}
/** /**
This trait defines new considerations. In general, you should use the [derive macro](derive.Consideration.html) instead. This trait defines new Scorers. In general, you should use the [derive macro](derive.Scorer.html) instead.
*/ */
#[typetag::deserialize] #[typetag::deserialize]
pub trait Scorer: std::fmt::Debug + Sync + Send { pub trait Scorer: std::fmt::Debug + Sync + Send {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment