Hands-on Rust: spawn entities doesn't honor the frequency (page 276)

While converting to data driven spawning the data specifies that an item (like a dungeon map) has a frequency of 1.

However, when they actually get spawned, it is potentially creating many more of them (or none of them). This appears to be because it is leveraging map_builder.spawn_monsters which is hard coded to

const NUM_MONSTERS: usize = 50;

So that means this code is iterating over the 50 Points, rather than over the number of items in the available_entities:

        spawn_points.iter().for_each(|pt| {
            if let Some(entity) = rng.random_slice_entry(&available_entities) {
                self.spawn_entity(pt, entity, &mut commands);
            }
        });

Did I miss something, or is this indeed what is happening?

Hi!

With hindsight, I should have used the rand crate’s weighted selection, but this seems to work. NUM_MONSTERS is meant to limit the total number of monsters - the actual weighting happens with available_entities.

At the top of spawn_entities (HandsOnRust/template.rs at main · thebracket/HandsOnRust · GitHub), available_entities is created by first filtering on level (so it only sees entities that can be on the level) and then inserting each entity a number of times equal to its frequency. So an orc with a frequency of 3 would be in available_entities 3 times.

So when random_slice_entry comes along and picks a slice entry, it includes all of the slice entries - including the duplicates. That preserves the weighting by template type.

Hope that makes sense?

Thanks a bunch for taking time to respond.

I guess the way it is working isn’t what I expected. Not wrong, just not what I expected. I expected that if I have frequency of 1, then only 1 of those would appear.
In this case, frequency ends up really just being a relative weighting compared to the other items as you iterate up to the limit contained in NUM_MONSTERS.

Walking through it:

In main State::new() we call let mut map_builder = MapBuilder::new(&mut rng); which figures out which Architect to use and calls new() on that.

The architect, using DrunkardsWalkArchitect in this example, creates a map_builder and then calls mb.monster_spawns = mb.spawn_monsters(&center, rng);

MapBuilder.spawn_monsters sets a const NUM_MONSTERS: usize = 50; and then

for _ in 0..NUM_MONSTERS {
            let target_index = rng.random_slice_index(&spawnable_tiles).unwrap();
            spawns.push(spawnable_tiles[target_index].clone());
            spawnable_tiles.remove(target_index);
        }

So at this point mb.monster_spawns has a vector of 50 Points where spawns are to take place.

Back in main.rs we call

spawn_player(&mut ecs, map_builder.player_start);
place_exit(&mut map_builder);
spawn_level(&mut ecs, &mut rng, 0, &map_builder.monster_spawns);

The call to spawn_level is passing in &map_builder.monster_spawns (our vector of 50 Points).

In spawn_level we load the template and call template.spawn_entities(ecs, rng, level, spawn_points); passing in the slice of 50 Points.

We do build up available_entities which is a flat structure where if something is listed with freqency n, then it will appear n times in the vector.

However, we aren’t looping over available_entities we are looping over our list of 50 spawn_points:

spawn_points.iter().for_each(|pt| {
            if let Some(entity) = rng.random_slice_entry(&available_entities) {
                self.spawn_entity(pt, entity, &mut commands);
            }
        });

This picks a random item from the available_entities slice.

However, this means that even if the Dungeon Map has a frequency of 1, it will likely be created multiple times or potentially not at all.

Again, not wrong… Just not what I was expecting and I was wondering if I missed removing something along the way.

Thanks again!

You’re welcome. My reasoning goes back to old D&D monster tables. You’d see big tables along the lines of:

Roll Monster
1 Big Orc
2-3 Little Orc
4-7 Goblin
8-10 Goblin Archer

For those, you’re setting a frequency with which something appears rather than a maximum number. A good extension would be a “max per level” field that also limits the number of appearances.

Ahh… yep, that makes sense. It is all about context. :smile:

Thanks again