├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.md ├── Makefile.toml ├── README.md ├── README.tpl ├── assets └── models │ └── town.glb ├── cliff.toml ├── derive ├── Cargo.toml └── src │ ├── action.rs │ ├── lib.rs │ └── scorer.rs ├── examples ├── concurrent.rs ├── custom_measure.rs ├── farming_sim.rs ├── one_off.rs ├── sequence.rs └── thirst.rs ├── src ├── actions.rs ├── choices.rs ├── evaluators.rs ├── lib.rs ├── measures.rs ├── pickers.rs ├── scorers.rs └── thinker.rs └── tests ├── derive_action.rs ├── derive_scorer.rs └── steps.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [zkat] 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | RUSTFLAGS: -Dwarnings 7 | 8 | jobs: 9 | fmt_and_docs: 10 | name: Check fmt & build docs 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Install Rust 15 | uses: dtolnay/rust-toolchain@master 16 | with: 17 | toolchain: stable 18 | components: rustfmt 19 | - name: rustfmt 20 | run: cargo fmt --all -- --check 21 | - name: docs 22 | run: cargo doc --no-deps 23 | 24 | build_and_test: 25 | name: Build & Test 26 | runs-on: ${{ matrix.os }} 27 | strategy: 28 | matrix: 29 | rust: [stable] 30 | os: [ubuntu-latest, macOS-latest, windows-latest] 31 | 32 | steps: 33 | - uses: actions/checkout@v3 34 | - name: Install Rust 35 | uses: dtolnay/rust-toolchain@master 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | components: clippy 39 | - if: matrix.os == 'ubuntu-latest' 40 | run: sudo apt-get install g++ pkg-config libx11-dev libasound2-dev libudev-dev 41 | - name: Clippy 42 | run: cargo clippy --all -- -D warnings 43 | - name: Run tests 44 | run: cargo test --all --verbose 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `big-brain` Release Changelog 2 | 3 | 4 | ## 0.22.0 (2024-11-30) 5 | 6 | ### Features 7 | 8 | * **picker:** add HighestToScore Picker (#105) ([ab0385d5](https://github.com/zkat/big-brain/commit/ab0385d543a764f8a06013dbb69ef2193e1ef8ab)) 9 | * **bevy:** Upgrade to bevy 0.15 (#106) ([868ebdb4](https://github.com/zkat/big-brain/commit/868ebdb4fc3116af095dcd1ea0685fa6c517ddfe)) 10 | 11 | 12 | ## 0.21.1 (2024-07-09) 13 | 14 | This is just a patch release because of a hiccup during the release process for 0.21.1, but is otherwise identical. 15 | 16 | 17 | ## 0.21.0 (2024-07-09) 18 | 19 | ### Features 20 | 21 | * **bevy:** Upgrade to bevy 0.14.0 (#101) ([3a952a24](https://github.com/zkat/big-brain/commit/3a952a244b01a09fa6157ae9349575935d159c91)) 22 | 23 | 24 | ## 0.20.0 (2024-05-17) 25 | 26 | ### Features 27 | 28 | * **bevy:** Update for bevy 0.13 (#93) ([50415e55](https://github.com/zkat/big-brain/commit/50415e55f0a9937dc99cb8f0b0906e47cb390082)) 29 | * **scorers:** Add support for generic types in ScorerBuilder (#96) ([98337faa](https://github.com/zkat/big-brain/commit/98337faacd5eb720cc820628fd1bfa07e6c8effa)) 30 | 31 | ### Bug Fixes 32 | 33 | * **derive:** using prelude rather than ecs::xyz in derive (#99) ([128f55e0](https://github.com/zkat/big-brain/commit/128f55e008c933a3eae1df44de0455f56acfdff7)) 34 | 35 | 36 | ## 0.19.0 (2024-01-19) 37 | 38 | ### Features 39 | 40 | * **set_unchecked:** Add `set_unchecked` method to `Score` (#78) ([e179cb53](https://github.com/zkat/big-brain/commit/e179cb53fa981a0cf08352590ce36799f7ee0235)) 41 | * **bevy:** Migrated to Bevy 0.12 (#86) ([69c3a4a5](https://github.com/zkat/big-brain/commit/69c3a4a506fb5bcb94e26d87cccdb27cc180429a)) 42 | * **bevy:** Update to bevy 0.12.1 (#87) ([6befa384](https://github.com/zkat/big-brain/commit/6befa3844aec54b31886ff52f3e9f596ea57e088)) 43 | * **thinker:** make ThinkerBuilder be Clone ([2e493d6b](https://github.com/zkat/big-brain/commit/2e493d6bb7339bd278a597555b707aa379eebbcb)) 44 | * **generics:** support generic actions (#88) ([7f5a3845](https://github.com/zkat/big-brain/commit/7f5a3845947e40f5a2c6460060eaef3ec5649804)) 45 | 46 | ### Bug Fixes 47 | 48 | * **despawn:** don't try to despawn if an entity doesn't exist ([bdf51c32](https://github.com/zkat/big-brain/commit/bdf51c3206e9afa6b35c1f5a199c6465c3086217)) 49 | 50 | 51 | ## 0.18.0 (2023-07-16) 52 | 53 | ### Features 54 | 55 | * **bevy:** Update to bevy 0.11 (#79) ([72bb861f](https://github.com/zkat/big-brain/commit/72bb861f9b17f7bc052ff33bf4e4a3ab2876775a)) 56 | 57 | 58 | ## 0.17.0 (2023-03-06) 59 | 60 | ### Features 61 | 62 | * **bevy:** Update to bevy 0.10 (#72) ([66ece025](https://github.com/zkat/big-brain/commit/66ece025a0b296decf8a0fe4c301b78bd0971d77)) 63 | 64 | 65 | ## 0.16.0 (2023-01-30) 66 | 67 | Probably the biggest change in this release is removal of the blanket 68 | `ActionBuilder` and `ScorerBuilder` implementations for `Clone` types. This is 69 | a fairly significant breaking change, but one that is fairly easy to resolve: 70 | simply use the new `#[derive(ActionBuilder)]` and `#[derive(ScorerBuilder)]` 71 | macros to derive the necessary implementations for your Action and Scorer 72 | Components and you should be good to go. 73 | 74 | ### Features 75 | 76 | * **derive:** Add derive macros for Action and Scorer (#65) ([359bccef](https://github.com/zkat/big-brain/commit/359bccef46f67d286c7f89cbe1b93b7b37b46588)) 77 | * **BREAKING CHANGE**: This gets rid of the blanket implementation for Action/ScorerBuilder on Clone things, and instead requires that people use derive macros (or manually implement the traits), if they want to go the clone-to-instantiate route. 78 | * **concurrenty:** Add ConcurrentMode configuration to Concurrently Action (#68) ([f6d04feb](https://github.com/zkat/big-brain/commit/f6d04feb18927250304554467da2cb1c6583fc2d)) 79 | * **reflection:** Implement Reflect trait for all relevant types (#69) ([31543c78](https://github.com/zkat/big-brain/commit/31543c78bd398e7781ced90701e73691b265a6ce)) 80 | 81 | ### Bug Fixes 82 | 83 | * **typo:** fix scorer argument name (#63) ([5be71335](https://github.com/zkat/big-brain/commit/5be71335b3a4429ed05c1d3e0969dc521400df09)) 84 | * **derive:** some tweaks to new derive macros ([4a622a90](https://github.com/zkat/big-brain/commit/4a622a90bf895d35ec255971b5ca7d1c5c95b120)) 85 | 86 | 87 | ## 0.15.0 (2022-11-13) 88 | 89 | ### Features 90 | 91 | * **deps:** Update to Bevy 0.9 (#59) ([8ce5cd1b](https://github.com/zkat/big-brain/commit/8ce5cd1b57a2de0c10bcbc1c1686187d680134d9)) 92 | 93 | ### Bug Fixes 94 | 95 | * **spans:** Make ActionSpan::new() private ([4486af1d](https://github.com/zkat/big-brain/commit/4486af1db83fc4fdf50acd2eacfa7d96e64501f3)) 96 | * **BREAKING CHANGE**: This function was previously public, but it was never meant to be used. 97 | 98 | 99 | ## 0.14.0 (2022-09-25) 100 | 101 | This is a fairly beefy release. The two main changes are the addition of 102 | significant new observability features, which let you debug/trace both 103 | big-brain and your own Scorers and Actions more easily in an integrated 104 | manner. Additionally, a new advanced composite Scorer was added, 105 | MeasuredScorer, which can be used to create some interesting behaviors when 106 | you have want to factor in multiple scorers when adding cases to Thinker. 107 | 108 | Besides that, there's several bugfixes to long-standing bugs and a couple of 109 | other features. 110 | 111 | Enjoy! 112 | 113 | ### Features 114 | 115 | * **entities:** Rename ActionEnt and ScorerEnt to Action and Scorer ([43f37959](https://github.com/zkat/big-brain/commit/43f3795929a4a67806a3a50113e9573d7dc95895)) 116 | * **BREAKING CHANGE**: These are externally-exported, so you'll have to rename them yourself, too 117 | * **tracing:** add tracing support for Thinker/Action/Scorer (#55) ([a32bc01d](https://github.com/zkat/big-brain/commit/a32bc01d4280bad060f998782f03cbf82ce481cf)) 118 | * **BREAKING CHANGE**: In the process of doing this, `spawn_action` and `spawn_scorer` were moved out of `ActionBuilder` and `ScorerBuilder` traits, respectively. This will likely affect any user-side composite actions and scorers. Use `scorers::spawn_scorer` and `actions::spawn_action` instead. 119 | * **thinker:** Add support for scheduling one-off Actions on a Thinker (#57) ([382d2014](https://github.com/zkat/big-brain/commit/382d20148d654ac65027e4ec68d964eb37364cac)) 120 | * **measures:** Implement MeasuredScorer and some initial measures (#54) ([c6a6c5c9](https://github.com/zkat/big-brain/commit/c6a6c5c9d685c14f13b3906097a7231a3d648718)) 121 | 122 | ### Bug Fixes 123 | 124 | * **actions,scorers:** Transform/GlobalTransform are no longer needed for hierarchies. ([df10f034](https://github.com/zkat/big-brain/commit/df10f034595d2f8800e4c68473c6039e4754a1ec)) 125 | * **tracing:** fix warnings and wrong cfg feature name ([5ff39632](https://github.com/zkat/big-brain/commit/5ff396326e236aa4b408503696cf46121183e520)) 126 | * **thinker:** `otherwise` clause no longer overrides running action (#56) ([849ab346](https://github.com/zkat/big-brain/commit/849ab3460e17c47796d2f5948b002973867411a1)) 127 | * **BREAKING CHANGE**: This patch changes the behavior for `otherwise` such that it won't override an existing action if it's still running, but it'll still execute as soon as that action finishes. I think this is really what people expect this to do, so let's give it a shot! 128 | * **tracing:** drop action span scope before spawning next action ([ee899e4a](https://github.com/zkat/big-brain/commit/ee899e4a841fbfaf3c1e3541171a7533cd339ec2)) 129 | 130 | 131 | ## 0.13.0 (2022-09-21) 132 | 133 | ### Features 134 | 135 | * **scorers:** make ScorerEnt public (#50) ([9e6d7f63](https://github.com/zkat/big-brain/commit/9e6d7f63f52f66ad6e80914913aad37c00570bb3)) 136 | * **scorers:** make FixedScore members pub (#49) ([f0ddb9e5](https://github.com/zkat/big-brain/commit/f0ddb9e51cf96956b73ca2d7fc25812def676fef)) 137 | * **picker:** Implement a Highest Picker (#52) ([4b48f94d](https://github.com/zkat/big-brain/commit/4b48f94d7221eedcaa1a64a6d2f100022f79164b)) 138 | * **scorers:** Add ProductOfScorers composite scorer (#51) ([e425e234](https://github.com/zkat/big-brain/commit/e425e2348a53cd675070b4f2a3e7076242012c8b)) 139 | * **actions:** make ActionEnt public, too ([fc30e752](https://github.com/zkat/big-brain/commit/fc30e752c66aa887f62a899827e5a8c9910eeec3)) 140 | * **prelude:** Add ScorerEnt and ActionEnt to the prelude ([08b0598b](https://github.com/zkat/big-brain/commit/08b0598b1cd1fa5cae68932012ff20d76d0acbf6)) 141 | 142 | 143 | ## 0.12.0 (2022-08-01) 144 | 145 | ### Features 146 | 147 | * **bevy:** Upgrade to Bevy 0.8. (#43) ([0f0c4479](https://github.com/zkat/big-brain/commit/0f0c4479ab84cc60dae3a8fdaab1d560807f0e3f)) 148 | 149 | 150 | ## 0.11.0 (2022-05-05) 151 | 152 | ### Features 153 | 154 | * **deps:** upgrade to Bevy 0.7. (#37) ([38688789](https://github.com/zkat/big-brain/commit/38688789d08547e1cbc0d2a373fc58af39dea360)) 155 | 156 | 157 | ## 0.10.0 (2022-01-15) 158 | 159 | This is my first pass at a significant API improvement. I iterated on it for a 160 | while and this is what I settled on. I look forward to continuing to evolve 161 | this API as I get more feedback and experience with it! Please let me know 162 | what you think! 163 | 164 | ### Breaking Changes 165 | 166 | * **thinker:** stop cancelling actions when they go under Picker thresholds ([4c72b3d1](https://github.com/zkat/big-brain/commit/4c72b3d11eaa42af4b99ccf9ea729306e589ada8)) 167 | * **stages:** Strongly typed stages for BigBrainPlugin ([65ca646e](https://github.com/zkat/big-brain/commit/65ca646e3b92b178025591878e2df6a08714880f)) 168 | * **builders:** Blanket impls for ActionBuilder and ScorerBuilder when Clone ([8bed75b5](https://github.com/zkat/big-brain/commit/8bed75b54a43c72b53fbf9e2605b942cb2c53214)) 169 | * **api:** rename attach and hide it from docs ([6c64df4f](https://github.com/zkat/big-brain/commit/6c64df4fc1211abe19919a3628476b930b6e9919)) 170 | * **choice:** return &Choice instead of cloning ([a76dcbb6](https://github.com/zkat/big-brain/commit/a76dcbb67d4f6ae402f03d22e8d526408d8d875f)) 171 | 172 | ### Features 173 | 174 | * **example:** update thirst example ([edecc4c9](https://github.com/zkat/big-brain/commit/edecc4c95f76bcd69c042372140486f744f4ccea)) 175 | 176 | ### Bug Fixes 177 | 178 | * **hierarchy:** make sure all the hierarchy stuff is set up properly ([372c13e2](https://github.com/zkat/big-brain/commit/372c13e207523ec919c6490682f628f7d21cebea)) 179 | * **tests:** update tests ([94e1b1f6](https://github.com/zkat/big-brain/commit/94e1b1f685e6ab0be9d90bae5dfbd648ba87f1de)) 180 | 181 | 182 | ## 0.9.0 (2022-01-09) 183 | 184 | ### Features 185 | 186 | * **deps:** update for bevy 0.6 (#31) ([b97f9273](https://github.com/zkat/big-brain/commit/b97f9273c5f5eceb010d8fa2b23abb534fb2cee1)) 187 | * **perf:** Use SparseSet for ActionState ([5fc08176](https://github.com/zkat/big-brain/commit/5fc081765c1ed8788a7a5d1e940efbc66dc8aa8f)) 188 | 189 | 190 | ## 0.8.0 (2021-09-19) 191 | 192 | ### Bug Fixes 193 | 194 | * **systems:** Fix steps, add a test and explicit systems ordering (#27) ([f33315c9](https://github.com/zkat/big-brain/commit/f33315c9b7b769a94baab17e3a9df9f5ebe924d2)) (credit: [@payload](https://github.com/payload)) 195 | 196 | 197 | ## 0.7.0 (2021-09-16) 198 | 199 | ### Bug Fixes 200 | 201 | * **deps:** Don't include Bevy default features when used as a dependency. (#25) ([61558137](https://github.com/zkat/big-brain/commit/615581370a165645795966ac7c878ff492630ba2)) 202 | 203 | ### Features 204 | 205 | * **license:** change license to Apache-2.0 ([d7d17772](https://github.com/zkat/big-brain/commit/d7d177729476af8ec1463d8957a35092a336098a)) 206 | * **BREAKING CHANGE**: This is a significant licensing change. Please review. 207 | 208 | 209 | ## 0.6.0 (2021-05-20) 210 | 211 | #### Features 212 | 213 | * **pickers:** Make choices mod public (#23) ([92034cd0](https://github.com/zkat/big-brain/commit/92034cd04e629723893cfcd7730ce597083da9e7)) 214 | * **scorers:** Added EvaluatingScorer (#24) ([1a1d5b3d](https://github.com/zkat/big-brain/commit/1a1d5b3d17d96a51084418128f0bfebe0ad8c702)) 215 | 216 | #### Bug Fixes 217 | 218 | * **actions:** Concurrently was not setting its state to Failure ([d4a689f6](https://github.com/zkat/big-brain/commit/d4a689f6c60f509a71fb3a9ae4ca49dad263acab)) 219 | 220 | 221 | 222 | ## 0.5.0 (2021-04-27) 223 | 224 | Got a few goodies in this release, mainly focused around composite actions and 225 | scorers, which were apparently broken. 226 | 227 | Shout out again to [@piedoomy](https://github.com/piedoomy) for some of these 228 | contributions! 229 | 230 | #### Features 231 | 232 | * **actions:** Add new Concurrently composite action ([6c736374](https://github.com/zkat/big-brain/commit/6c736374b4afd60af592a357ad2403304d3638d1)) 233 | * **evaluators:** added inversed linear evaluator helper (#19) ([f871d19e](https://github.com/zkat/big-brain/commit/f871d19e93b6764088d6db5db1947fcb37143868)) 234 | * **scorers:** Added WinningScorer composite scorer (#20) ([748b30ae](https://github.com/zkat/big-brain/commit/748b30aedcb0711f4180a8e24b457f01f0b84f6a)) 235 | 236 | #### Breaking Changes 237 | 238 | * **scorers:** Composite Scorers now all use `.push()` instead of a mixture of `.push()` and `.when()`. Please update any usages of composite scorers ([63bad1fd](https://github.com/zkat/big-brain/commit/63bad1fd2c82eadc88107003dd819f3cfa7530a2) 239 | 240 | #### Bug Fixes 241 | 242 | * **scorers:** 243 | * Scorer builders now properly return themselves ([63bad1fd](https://github.com/zkat/big-brain/commit/63bad1fd2c82eadc88107003dd819f3cfa7530a2) 244 | * Fixed error where wrong component for `SumOfScorers` was attached (#21) ([71fd05a6](https://github.com/zkat/big-brain/commit/71fd05a64912b2cc88c76439543ea00a00267303)) 245 | 246 | 247 | 248 | 249 | ## 0.4.0 (2021-04-26) 250 | 251 | #### Breaking Changes 252 | 253 | * **score:** scores are now 0.0..=1.0, not 0.0..=100.0 ([71f5122e](https://github.com/zkat/big-brain/commit/71f5122e9f5aa5b5965ad67f53ae9850f487d167), breaks [#](https://github.com/zkat/big-brain/issues/)) 254 | 255 | #### Features 256 | 257 | * **evaluators:** Make all evaluators Clone ([4d5a5121](https://github.com/zkat/big-brain/commit/4d5a512171bf6f850893424c5baad03b0e686c26)) 258 | 259 | 260 | 261 | ## 0.3.5 (2021-04-24) 262 | 263 | Previously, if a `Picker` re-picked the same action, and that action had been 264 | set to `Success` or `Failure`, it would just keep running the action in that 265 | state until it was time to switch to a different one. 266 | 267 | With this version, that behavior is changed, and `Failure` and `Success` 268 | actions that are re-picked will be respawned entirely (not even reused). 269 | 270 | Cheers to [@piedoomy](https://github.com/piedoomy) on Discord for pointing out 271 | this weird behavior! 272 | 273 | #### Bug Fixes 274 | 275 | * **thinker:** launch a new action when the current action is in an end state ([80d23f2f](https://github.com/zkat/big-brain/commit/80d23f2f2337a863c9cc3afbf944b25e3911db8c)) 276 | 277 | 278 | 279 | ## 0.3.4 (2021-04-23) 280 | 281 | Welp. Turns out Thinkers were completely broken? This should work better :) 282 | 283 | #### Bug Fixes 284 | 285 | * **prelude:** Export ThinkerBuilder from prelude ([06cc03e1](https://github.com/zkat/big-brain/commit/06cc03e1dd563c708bff276f7a194c8c81a00a5a)) 286 | * **thinker:** 287 | * disposed of ActiveThinker and circular state-setting ([7f8ed12b](https://github.com/zkat/big-brain/commit/7f8ed12b112152c3f8d548d0a2208cefdb1581af)) 288 | * Need to do proper ptr_eq comparison here ([037a7c0d](https://github.com/zkat/big-brain/commit/037a7c0d0da065ea4cb5642047302d6bda13c670)) 289 | 290 | 291 | 292 | ## 0.3.3 (2021-04-18) 293 | 294 | This fixes an issue with more children being added to an Actor causing Thinkers to get clobbered in really annoying ways. 295 | 296 | #### Bug Fixes 297 | 298 | * **thinker:** stop using the child/parent system for toplevel thinkers ([db16e2f6](https://github.com/zkat/big-brain/commit/db16e2f6ee97777b4df12e4ae435bf27b8012c7c)) 299 | 300 | 301 | 302 | ## 0.3.2 (2021-04-17) 303 | 304 | This is a quick bugfix. Shoutout to [@ndarilek](https://github.com/ndarilek) 305 | for finding this one and giving me a chance to debug it! 306 | 307 | tl;dr: Bevy's hierarchy system *requires* that all children have `Transform` 308 | and `GlobalTransform` also attached, otherwise it just... kills them. 309 | 310 | #### Bug Fixes 311 | 312 | * **scorer:** stop attaching duplicate scorers ([10a6d022](https://github.com/zkat/big-brain/commit/10a6d022ec682e33b98309318020c9068be4cea2)) 313 | * **thinker:** add Transform and GlobalTransform ([ed3a7cb3](https://github.com/zkat/big-brain/commit/ed3a7cb3c03e27b76b374f75ac179f29c979e4cf)) 314 | 315 | 316 | ## 0.3.1 (2021-04-14) 317 | 318 | Just a quick release because I broke docs.rs :) 319 | 320 | #### Bug Fixes 321 | 322 | * **build:** remove .cargo/config.toml to make docs.rs happy ([393d9807](https://github.com/zkat/big-brain/commit/393d9807576d21c7234667b1f9914f1886579bd0)) 323 | 324 | 325 | 326 | ## 0.3.0 (2021-04-14) 327 | 328 | This is a major overhaul of the Bevy API. It removes `.ron` support 329 | altogether, in favor of plain old Rust builders. 330 | 331 | #### Breaking Changes 332 | 333 | * The `.ron` Thinker definition API is gone. Use the ThinkerBuilder API instead. 334 | * The `Action` and `Scorer` derive macros are gone, including all of `big_brain_derive`. 335 | * Measures and Weights are gone. 336 | * `big-brain` no longer exports everything from the toplevel. Use `big_brain::prelude::*` instead. 337 | 338 | #### Bug Fixes 339 | 340 | Probably. 341 | 342 | #### Features 343 | 344 | * New builder-based Thinker API! 345 | * Composite Scorers: `FixedScore`, `AllOrNothing`, and `SumOfScorers`. 346 | * Composite Action: `Steps`, for sequential Action execution. 347 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "big-brain" 3 | version = "0.23.0" 4 | authors = ["Kat Marchán "] 5 | edition = "2021" 6 | description = "Rusty Utility AI library" 7 | exclude = ["assets/"] 8 | license = "Apache-2.0" 9 | readme = "README.md" 10 | keywords = ["utility-ai", "bevy", "ai", "ecs"] 11 | categories = ["game-development"] 12 | repository = "https://github.com/zkat/big-brain" 13 | homepage = "https://github.com/zkat/big-brain" 14 | 15 | [workspace] 16 | 17 | [dependencies] 18 | bevy = { version = "0.16.0", default-features = false, features = ["bevy_log"] } 19 | big-brain-derive = { version = "=0.23.0", path = "./derive" } 20 | 21 | [dev-dependencies] 22 | bevy = { version = "0.16.0", default-features = true } 23 | rand = { version = "0.8.5", features = ["small_rng"] } 24 | 25 | [features] 26 | trace = [] 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 big-brain Contributors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.changelog] 2 | workspace=false 3 | install_crate="git-cliff" 4 | command = "git-cliff" 5 | args = ["--prepend", "CHANGELOG.md", "-u", "--tag", "${@}"] 6 | 7 | [tasks.release] 8 | workspace=false 9 | install_crate="cargo-release" 10 | command = "cargo" 11 | args = ["release", "--workspace", "${@}"] 12 | 13 | [tasks.readme] 14 | workspace=false 15 | install_crate="cargo-readme" 16 | command = "cargo" 17 | args = ["readme", "-o", "README.md"] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `big-brain`` 2 | 3 | [![crates.io](https://img.shields.io/crates/v/big-brain.svg)](https://crates.io/crates/big-brain) 4 | [![docs.rs](https://docs.rs/big-brain/badge.svg)](https://docs.rs/big-brain) 5 | [![Apache 6 | 2.0](https://img.shields.io/badge/license-Apache-blue.svg)](./LICENSE.md) 7 | 8 | `big-brain` is a [Utility 9 | AI](https://en.wikipedia.org/wiki/Utility_system) library for games, built 10 | for the [Bevy Game Engine](https://bevyengine.org/) 11 | 12 | It lets you define complex, intricate AI behaviors for your entities based 13 | on their perception of the world. Definitions are heavily data-driven, 14 | using plain Rust, and you only need to program Scorers (entities that look 15 | at your game world and come up with a Score), and Actions (entities that 16 | perform actual behaviors upon the world). No other code is needed for 17 | actual AI behavior. 18 | 19 | See [the documentation](https://docs.rs/big-brain) for more details. 20 | 21 | #### Features 22 | 23 | * Highly concurrent/parallelizable evaluation. 24 | * Integrates smoothly with Bevy. 25 | * Proven game AI model. 26 | * Highly composable and reusable. 27 | * State machine-style continuous actions/behaviors. 28 | * Action cancellation. 29 | 30 | #### Example 31 | 32 | As a developer, you write application-dependent code to define 33 | [`Scorers`](#scorers) and [`Actions`](#actions), and then put it all 34 | together like building blocks, using [`Thinkers`](#thinkers) that will 35 | define the actual behavior. 36 | 37 | ##### Scorers 38 | 39 | `Scorer`s are entities that look at the world and evaluate into `Score` 40 | values. You can think of them as the "eyes" of the AI system. They're a 41 | highly-parallel way of being able to look at the `World` and use it to 42 | make some decisions later. 43 | 44 | ```rust 45 | use bevy::prelude::*; 46 | use big_brain::prelude::*; 47 | 48 | #[derive(Debug, Clone, Component, ScorerBuilder)] 49 | pub struct Thirsty; 50 | 51 | pub fn thirsty_scorer_system( 52 | thirsts: Query<&Thirst>, 53 | mut query: Query<(&Actor, &mut Score), With>, 54 | ) { 55 | for (Actor(actor), mut score) in query.iter_mut() { 56 | if let Ok(thirst) = thirsts.get(*actor) { 57 | score.set(thirst.thirst); 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | ##### Actions 64 | 65 | `Action`s are the actual things your entities will _do_. They are 66 | connected to `ActionState`s that represent the current execution state of 67 | the state machine. 68 | 69 | ```rust 70 | use bevy::prelude::*; 71 | use big_brain::prelude::*; 72 | 73 | #[derive(Debug, Clone, Component, ActionBuilder)] 74 | pub struct Drink; 75 | 76 | fn drink_action_system( 77 | mut thirsts: Query<&mut Thirst>, 78 | mut query: Query<(&Actor, &mut ActionState), With>, 79 | ) { 80 | for (Actor(actor), mut state) in query.iter_mut() { 81 | if let Ok(mut thirst) = thirsts.get_mut(*actor) { 82 | match *state { 83 | ActionState::Requested => { 84 | thirst.thirst = 10.0; 85 | *state = ActionState::Success; 86 | } 87 | ActionState::Cancelled => { 88 | *state = ActionState::Failure; 89 | } 90 | _ => {} 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | ##### Thinkers 98 | 99 | Finally, you can use it when define the `Thinker`, which you can attach as 100 | a regular Component: 101 | 102 | ```rust 103 | fn spawn_entity(cmd: &mut Commands) { 104 | cmd.spawn(( 105 | Thirst(70.0, 2.0), 106 | Thinker::build() 107 | .picker(FirstToScore { threshold: 0.8 }) 108 | .when(Thirsty, Drink), 109 | )); 110 | } 111 | ``` 112 | 113 | ##### App 114 | 115 | Once all that's done, we just add our systems and off we go! 116 | 117 | ```rust 118 | fn main() { 119 | App::new() 120 | .add_plugins(DefaultPlugins) 121 | .add_plugins(BigBrainPlugin::new(PreUpdate)) 122 | .add_systems(Startup, init_entities) 123 | .add_systems(Update, thirst_system) 124 | .add_systems(PreUpdate, drink_action_system.in_set(BigBrainSet::Actions)) 125 | .add_systems(PreUpdate, thirsty_scorer_system.in_set(BigBrainSet::Scorers)) 126 | .run(); 127 | } 128 | ``` 129 | 130 | #### bevy version and MSRV 131 | 132 | The current version of `big-brain` is compatible with `bevy` 0.16.0. 133 | 134 | The Minimum Supported Rust Version for `big-brain` should be considered to 135 | be the same as `bevy`'s, which as of the time of this writing was "the 136 | latest stable release". 137 | 138 | #### Reflection 139 | 140 | All relevant `big-brain` types implement the bevy `Reflect` trait, so you 141 | should be able to get some useful display info while using things like 142 | [`bevy_inspector_egui`](https://crates.io/crates/bevy_inspector_egui). 143 | 144 | This implementation should **not** be considered stable, and individual 145 | fields made visible may change at **any time** and not be considered 146 | towards semver. Please use this feature **only for debugging**. 147 | 148 | #### Contributing 149 | 150 | 1. Install the latest Rust toolchain (stable supported). 151 | 2. `cargo run --example thirst` 152 | 3. Happy hacking! 153 | 154 | #### License 155 | 156 | This project is licensed under [the Apache-2.0 License](LICENSE.md). 157 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # `{{crate}}`` 2 | 3 | {{readme}} 4 | -------------------------------------------------------------------------------- /assets/models/town.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zkat/big-brain/7d3911c15078db332b97a91886de02d8c9bdd9b4/assets/models/town.glb -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # configuration file for git-cliff (0.1.0) 2 | 3 | [changelog] 4 | # changelog header 5 | header = """ 6 | # `big-brain` Release Changelog 7 | 8 | """ 9 | 10 | # template for the changelog body 11 | # https://tera.netlify.app/docs/#introduction 12 | body = """ 13 | {% if version %}\ 14 | 15 | ## {{ version | replace(from="v", to="") }} ({{ timestamp | date(format="%Y-%m-%d") }}) 16 | {% else %}\ 17 | ## Unreleased 18 | {% endif %}\ 19 | {% for group, commits in commits | filter(attribute="scope") | group_by(attribute="group") %} 20 | ### {{ group | upper_first }} 21 | {% for commit in commits %} 22 | {% if commit.scope %}\ 23 | * **{{ commit.scope }}:** {{ commit.message }} ([{{ commit.id | truncate(length=8, end="") }}](https://github.com/zkat/big-brain/commit/{{ commit.id }})) 24 | {%- if commit.breaking %} 25 | * **BREAKING CHANGE**: {{ commit.breaking_description }} 26 | {%- endif %}\ 27 | {% endif %}\ 28 | {% endfor %} 29 | {% endfor %} 30 | """ 31 | 32 | # remove the leading and trailing whitespace from the template 33 | trim = false 34 | 35 | # changelog footer 36 | # footer = """ 37 | # 38 | # """ 39 | 40 | [git] 41 | # allow only conventional commits 42 | # https://www.conventionalcommits.org 43 | conventional_commits = true 44 | # regex for parsing and grouping commits 45 | commit_parsers = [ 46 | { message = "^feat*", group = "Features"}, 47 | { message = "^fix*", group = "Bug Fixes"}, 48 | { message = "^doc*", group = "Documentation"}, 49 | { message = "^perf*", group = "Performance"}, 50 | { message = "^refactor*", group = "Refactor"}, 51 | { message = "^style*", group = "Styling"}, 52 | { message = "^test*", group = "Testing"}, 53 | { message = "^chore\\(release\\): prepare for*", skip = true}, 54 | { message = "^chore*", group = "Miscellaneous Tasks"}, 55 | { body = ".*security", group = "Security"}, 56 | ] 57 | # filter out the commits that are not matched by commit parsers 58 | filter_commits = true 59 | # glob pattern for matching git tags 60 | # tag_pattern = "v?[0-9]*" 61 | # regex for skipping tags 62 | # skip_tags = "v0.1.0-beta.1" 63 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "big-brain-derive" 3 | version = "0.23.0" 4 | authors = ["Kat Marchán "] 5 | description = "Procedural macros to simplify implementation of Big Brain traits" 6 | documentation = "https://docs.rs/big-brain-derive" 7 | homepage = "https://github.com/zkat/big-brain" 8 | repository = "https://github.com/zkat/big-brain" 9 | keywords = ["utility-ai", "bevy", "ai", "ecs"] 10 | categories = ["game-development"] 11 | license = "Apache-2.0" 12 | edition = "2021" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [features] 18 | 19 | [dependencies] 20 | proc-macro2 = "1.0" 21 | syn = { version = "1.0", features = ["parsing"]} 22 | quote = "1.0" 23 | -------------------------------------------------------------------------------- /derive/src/action.rs: -------------------------------------------------------------------------------- 1 | //! Derive ActionBuilder on a given struct 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::{parse_macro_input, DeriveInput, Ident, Lit, LitStr, Meta}; 5 | 6 | /// Derive ActionBuilder on a struct that implements Component + Clone 7 | pub fn action_builder_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 8 | let input = parse_macro_input!(input as DeriveInput); 9 | 10 | let label = get_label(&input); 11 | 12 | let component_name = input.ident; 13 | let generics = input.generics; 14 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 15 | 16 | let component_string = component_name.to_string(); 17 | let build_method = build_method(&component_name, &ty_generics); 18 | let label_method = label_method( 19 | label.unwrap_or_else(|| LitStr::new(&component_string, component_name.span())), 20 | ); 21 | 22 | let gen = quote! { 23 | impl #impl_generics ::big_brain::actions::ActionBuilder for #component_name #ty_generics #where_clause { 24 | #build_method 25 | #label_method 26 | } 27 | }; 28 | 29 | proc_macro::TokenStream::from(gen) 30 | } 31 | 32 | fn get_label(input: &DeriveInput) -> Option { 33 | let mut label: Option = None; 34 | let attrs = &input.attrs; 35 | for option in attrs { 36 | let option = option.parse_meta().unwrap(); 37 | if let Meta::NameValue(meta_name_value) = option { 38 | let path = meta_name_value.path; 39 | let lit = meta_name_value.lit; 40 | if let Some(ident) = path.get_ident() { 41 | if ident == "action_label" { 42 | if let Lit::Str(lit_str) = lit { 43 | label = Some(lit_str); 44 | } else { 45 | panic!("Must specify a string for the `action_label` attribute") 46 | } 47 | } 48 | } 49 | } 50 | } 51 | label 52 | } 53 | 54 | fn build_method(component_name: &Ident, ty_generics: &syn::TypeGenerics) -> TokenStream { 55 | let turbofish = ty_generics.as_turbofish(); 56 | 57 | quote! { 58 | fn build(&self, cmd: &mut ::bevy::prelude::Commands, action: ::bevy::prelude::Entity, _actor: ::bevy::prelude::Entity) { 59 | cmd.entity(action).insert(#component_name #turbofish::clone(self)); 60 | } 61 | } 62 | } 63 | 64 | fn label_method(label: LitStr) -> TokenStream { 65 | quote! { 66 | fn label(&self) -> ::std::option::Option<&str> { 67 | ::std::option::Option::Some(#label) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Big Brain Derive 2 | //! Procedural macros to simplify the implementation of Big Brain traits 3 | mod action; 4 | mod scorer; 5 | 6 | use action::action_builder_impl; 7 | use scorer::scorer_builder_impl; 8 | 9 | /// Derives ActionBuilder for a struct that implements Component + Clone 10 | #[proc_macro_derive(ActionBuilder, attributes(action_label))] 11 | pub fn action_builder_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 12 | action_builder_impl(input) 13 | } 14 | 15 | /// Derives ScorerBuilder for a struct that implements Component + Clone 16 | #[proc_macro_derive(ScorerBuilder, attributes(scorer_label))] 17 | pub fn scorer_builder_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 18 | scorer_builder_impl(input) 19 | } 20 | -------------------------------------------------------------------------------- /derive/src/scorer.rs: -------------------------------------------------------------------------------- 1 | //! Derive ScorerBuilder on a given struct 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::{parse_macro_input, DeriveInput, Ident, Lit, LitStr, Meta}; 5 | 6 | /// Derive ScorerBuilder on a struct that implements Component + Clone 7 | pub fn scorer_builder_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 8 | let input = parse_macro_input!(input as DeriveInput); 9 | 10 | let label = get_label(&input); 11 | 12 | let component_name = input.ident; 13 | let generics = input.generics; 14 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 15 | 16 | let component_string = component_name.to_string(); 17 | let build_method = build_method(&component_name, &ty_generics); 18 | let label_method = label_method( 19 | label.unwrap_or_else(|| LitStr::new(&component_string, component_name.span())), 20 | ); 21 | 22 | let gen = quote! { 23 | impl #impl_generics ::big_brain::scorers::ScorerBuilder for #component_name #ty_generics #where_clause { 24 | #build_method 25 | #label_method 26 | } 27 | }; 28 | 29 | proc_macro::TokenStream::from(gen) 30 | } 31 | 32 | fn get_label(input: &DeriveInput) -> Option { 33 | let mut label: Option = None; 34 | let attrs = &input.attrs; 35 | for option in attrs { 36 | let option = option.parse_meta().unwrap(); 37 | if let Meta::NameValue(meta_name_value) = option { 38 | let path = meta_name_value.path; 39 | let lit = meta_name_value.lit; 40 | if let Some(ident) = path.get_ident() { 41 | if ident == "scorer_label" { 42 | if let Lit::Str(lit_str) = lit { 43 | label = Some(lit_str); 44 | } else { 45 | panic!("Must specify a string for the `scorer_label` attribute") 46 | } 47 | } 48 | } 49 | } 50 | } 51 | label 52 | } 53 | 54 | fn build_method(component_name: &Ident, ty_generics: &syn::TypeGenerics) -> TokenStream { 55 | let turbofish = ty_generics.as_turbofish(); 56 | 57 | quote! { 58 | fn build(&self, cmd: &mut ::bevy::prelude::Commands, scorer: ::bevy::prelude::Entity, _actor: ::bevy::prelude::Entity) { 59 | cmd.entity(scorer).insert(#component_name #turbofish::clone(self)); 60 | } 61 | } 62 | } 63 | 64 | fn label_method(label: LitStr) -> TokenStream { 65 | quote! { 66 | fn label(&self) -> ::std::option::Option<&str> { 67 | ::std::option::Option::Some(#label) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/concurrent.rs: -------------------------------------------------------------------------------- 1 | //! This example describes how to create a composite action that executes multiple sub-actions 2 | //! concurrently. 3 | //! 4 | //! `Race` succeeds when any of the sub-actions succeed. 5 | //! `Join` succeeds if all the sub-actions succeed. 6 | 7 | use bevy::log::LogPlugin; 8 | use bevy::prelude::*; 9 | use big_brain::prelude::*; 10 | use rand::rngs::SmallRng; 11 | use rand::{Rng, SeedableRng}; 12 | 13 | /// An action where the actor has to guess a given number 14 | #[derive(Clone, Component, Debug, ActionBuilder)] 15 | pub struct GuessNumber { 16 | // Number to guess (between 0 and 10 included) 17 | to_guess: u8, 18 | // Rng to perform guesses 19 | rng: SmallRng, 20 | } 21 | 22 | fn guess_number_action( 23 | // A query on all current MoveToWaterSource actions. 24 | mut action_query: Query<(&Actor, &mut ActionState, &mut GuessNumber, &ActionSpan)>, 25 | ) { 26 | // Loop through all actions, just like you'd loop over all entities in any other query. 27 | for (_actor, mut action_state, mut guess_number, span) in &mut action_query { 28 | let _guard = span.span().enter(); 29 | 30 | // Different behavior depending on action state. 31 | match *action_state { 32 | // Action was just requested; it hasn't been seen before. 33 | ActionState::Requested => { 34 | debug!( 35 | "Let's try to guess the secret number: {:?}", 36 | guess_number.to_guess 37 | ); 38 | // We don't really need any initialization code here, since the queries are cheap enough. 39 | *action_state = ActionState::Executing; 40 | } 41 | ActionState::Executing => { 42 | // Guess a number. If we guessed right, succeed; else keep trying. 43 | let guess: u8 = guess_number.rng.gen_range(0..=10); 44 | debug!("Guessed: {:?}", guess); 45 | if guess == guess_number.to_guess { 46 | debug!( 47 | "Guessed the secret number: {:?}! Action succeeded.", 48 | guess_number.to_guess 49 | ); 50 | *action_state = ActionState::Success; 51 | } 52 | } 53 | ActionState::Cancelled => { 54 | // Always treat cancellations, or we might keep doing this forever! 55 | // You don't need to terminate immediately, by the way, this is only a flag that 56 | // the cancellation has been requested. If the actor is balancing on a tightrope, 57 | // for instance, you may let them walk off before ending the action. 58 | *action_state = ActionState::Failure; 59 | } 60 | _ => {} 61 | } 62 | } 63 | } 64 | 65 | // We will use a dummy scorer that always returns 1.0 66 | #[derive(Clone, Component, Debug, ScorerBuilder)] 67 | pub struct DummyScorer; 68 | 69 | pub fn dummy_scorer_system(mut query: Query<(&Actor, &mut Score), With>) { 70 | for (Actor(_actor), mut score) in &mut query { 71 | score.set(1.0); 72 | } 73 | } 74 | 75 | pub fn init_entities(mut cmd: Commands) { 76 | let number_to_guess: u8 = 5; 77 | // We use the Race struct to build a composite action that will try to guess 78 | // multiple numbers. If any of the guesses are right, the whole `Race` action succeeds. 79 | let race_guess_numbers = Concurrently::build() 80 | .mode(ConcurrentMode::Race) 81 | .label("RaceToGuessNumbers") 82 | // ...try to guess a first number 83 | .push(GuessNumber { 84 | to_guess: number_to_guess, 85 | rng: SmallRng::from_entropy(), 86 | }) 87 | // ...try to guess a second number 88 | .push(GuessNumber { 89 | to_guess: number_to_guess, 90 | rng: SmallRng::from_entropy(), 91 | }); 92 | 93 | // We use the Join struct to build a composite action that will try to guess 94 | // multiple numbers. If all of the guesses are right, the whole `Race` action succeeds. 95 | let join_guess_numbers = Concurrently::build() 96 | .mode(ConcurrentMode::Join) // This is the default mode, so we could have omitted it. 97 | .label("JoinToGuessNumbers") 98 | // ...try to guess a first number 99 | .push(GuessNumber { 100 | to_guess: number_to_guess, 101 | rng: SmallRng::from_entropy(), 102 | }) 103 | // ...try to guess a second number 104 | .push(GuessNumber { 105 | to_guess: number_to_guess, 106 | rng: SmallRng::from_entropy(), 107 | }); 108 | 109 | // We'll use `Steps` to execute a sequence of actions. 110 | // First, we'll guess the numbers with 'Race', and then we'll guess the numbers with 'Join' 111 | // See the `sequence.rs` example for more details. 112 | let guess_numbers = Steps::build() 113 | .label("RaceAndThenJoin") 114 | .step(race_guess_numbers) 115 | .step(join_guess_numbers); 116 | 117 | // Build the thinker 118 | let thinker = Thinker::build() 119 | .label("GuesserThinker") 120 | // always select the action with the highest score 121 | .picker(Highest) 122 | .when(DummyScorer, guess_numbers); 123 | 124 | cmd.spawn(thinker); 125 | } 126 | 127 | fn main() { 128 | // Once all that's done, we just add our systems and off we go! 129 | App::new() 130 | .add_plugins(MinimalPlugins) 131 | .add_plugins(LogPlugin { 132 | // Use `RUST_LOG=big_brain=trace,concurrent=trace cargo run --example concurrent --features=trace` to see extra tracing output. 133 | filter: "big_brain=warn,concurrent=debug".to_string(), 134 | ..default() 135 | }) 136 | .add_plugins(BigBrainPlugin::new(PreUpdate)) 137 | .add_systems(Startup, init_entities) 138 | .add_systems( 139 | PreUpdate, 140 | ( 141 | guess_number_action.in_set(BigBrainSet::Actions), 142 | dummy_scorer_system.in_set(BigBrainSet::Scorers), 143 | ), 144 | ) 145 | .run(); 146 | } 147 | -------------------------------------------------------------------------------- /examples/custom_measure.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how to build a custom measure and use that 2 | //! in a Thinker. 3 | 4 | use bevy::ecs::component::Mutable; 5 | use bevy::log::LogPlugin; 6 | use bevy::prelude::*; 7 | use big_brain::prelude::*; 8 | use big_brain::scorers::MeasuredScorer; 9 | 10 | // Lets define a custom measure. There are quite a few built-in ones in big-brain, 11 | // so we'll create a slightly useless Measure that sums together the weighted scores, 12 | // but weights get divided by the Scorer's index in the Vec. 13 | #[derive(Debug, Clone)] 14 | pub struct SumWithDecreasingWeightMeasure; 15 | 16 | impl Measure for SumWithDecreasingWeightMeasure { 17 | fn calculate(&self, scores: Vec<(&Score, f32)>) -> f32 { 18 | scores 19 | .iter() 20 | .enumerate() 21 | .fold(0f32, |acc, (idx, (score, weight))| { 22 | acc + score.get() * weight / (1.0 + idx as f32) 23 | }) 24 | } 25 | } 26 | 27 | // We'll keep this example fairly simple, let's have Waffles and Pancakes, and 28 | // try to optimise our happiness based on keeping our waffle and pancake level high. 29 | // Its kind of like the thirst example but sweeter. 30 | #[derive(Component, Debug)] 31 | pub struct Pancakes(pub f32); 32 | 33 | #[derive(Component, Debug)] 34 | pub struct Waffles(pub f32); 35 | 36 | pub fn eat_dessert(time: Res