Reign

We've had plenty of time to think of a way for our Player to get back at those pesky Friends.
Sometimes, the simplest solution is the best.
If the Friends can overpower the Player when they are fully grown, we shouldn't let them reach that size.
I'm sure the Player can overcome Friend that are smaller than them.

use shipyard::{
    Component, EntitiesView, IntoIter, IntoWithId, Unique, UniqueView, UniqueViewMut, View,
    ViewMut, World,
};

#[derive(Component)]
struct ToDelete;

fn collision(
    entities: EntitiesView,
    mut player: UniqueViewMut<Player>,
    v_friend: View<Friend>,
    mut vm_to_delete: ViewMut<ToDelete>,
) -> Result<(), GameOver> {
    for (eid, friend) in v_friend.iter().with_id() {
        if friend.0.size == MAX_SIZE && friend.0.collide(&player.square) {
            // -- SNIP --
        } else if player.square.size >= friend.0.size && player.square.collide(&friend.0) {
            player.square.size = (player.square.size + INIT_SIZE / 2.).min(MAX_SIZE - 0.01);
            entities.add_component(eid, &mut vm_to_delete, ToDelete);
        }
    }

    Ok(())
}

It appears our Player can even overcome Friends of equal size.
By... eating them!?

Remember when we added Friends to the World, each one was assigned an EntityId.
We can iterate over both components and the EntityId of the entity that owns them by using with_id.

Then we can use this EntityId to add another component to the vanquished Friends.
As you may have noticed we are not modifying entities. We only need it to check that the eid is alive.

ToDelete is not a special component, we still have to make it do its job.

use shipyard::{
    AllStoragesViewMut, Component, EntitiesView, IntoIter, IntoWithId, SparseSet, Unique,
    UniqueView, UniqueViewMut, View, ViewMut, World,
};

async fn main() {
    // -- SNIP --

    loop {
        clear_background(WHITE);

        world.run(move_player);
        world.run(move_friends);
        world.run(grow);
        if world.run(collision).is_err() {
            panic!("Murder");
        }
        world.run(clean_up);
        world.run(render);

        next_frame().await
    }
}

fn clean_up(mut all_storages: AllStoragesViewMut) {
    all_storages.delete_any::<SparseSet<ToDelete>>();
}

AllStorages is the part of World that stores all components and entities.
We are using it to delete_any entity that has a ToDelete component in a SparseSet storage.
SparseSet is the storage for all Components. Uniques have a different storage and you can add custom storages to the World but that's an advanced feature.

It's over

Defeating smaller Friends is nice but most of the time they've grown by the time the Player reaches them.
The Player needs more power.

use shipyard::{
    AllStoragesViewMut, Component, EntitiesView, EntitiesViewMut, IntoIter, IntoWithId, SparseSet,
    Unique, UniqueView, UniqueViewMut, View, ViewMut, World,
};

const POWER_PELLET_SPAWN_RATE: u32 = 150;

async fn main() {
    // -- SNIP --

    let player = Player {
        square: Square {
            x,
            y,
            size: INIT_SIZE * 3.0,
        },
        pellet_counter: 0,
    };

    // -- SNIP --

    loop {
        // -- SNIP --

        world.run(grow);
        world.run(counters);
        world.run(spawn);
        if world.run(collision).is_err() {
            panic!("Murder");
        }

        // -- SNIP --
    }
}

struct Player {
    square: Square,
    pellet_counter: u32,
}

impl Player {
    fn power_up(&mut self) {
        self.pellet_counter = 120;
    }

    fn is_powered_up(&self) -> bool {
        self.pellet_counter > 0
    }
}

#[derive(Component)]
struct PowerPellet(Square);

fn render(player: UniqueView<Player>, v_friend: View<Friend>, v_power_pellets: View<PowerPellet>) {
    for pellet in v_power_pellets.iter() {
        pellet.0.render(YELLOW);
    }

    // -- SNIP --

    if player.is_powered_up() {
        player.square.render(YELLOW);
    } else {
        player.square.render(BLUE);
    }
}


fn collision(
    entities: EntitiesView,
    mut player: UniqueViewMut<Player>,
    v_friend: View<Friend>,
    v_power_pellets: View<PowerPellet>,
    mut vm_to_delete: ViewMut<ToDelete>,
) -> Result<(), GameOver> {
    for (eid, pellet) in v_power_pellets.iter().with_id() {
        if player.square.collide(&pellet.0) {
            player.power_up();
            entities.add_component(eid, &mut vm_to_delete, ToDelete);
        }
    }

    for (eid, friend) in v_friend.iter().with_id() {
        if friend.0.size == MAX_SIZE && friend.0.collide(&player.square) {
            if player.is_powered_up() {
                player.square.size = (player.square.size + INIT_SIZE / 2.).min(MAX_SIZE - 0.01);
                entities.add_component(eid, &mut vm_to_delete, ToDelete);

                continue;
            }

            player.square.size -= INIT_SIZE / 2.;

            // -- SNIP --
        } else if player.square.size >= friend.0.size && player.square.collide(&friend.0) {
            // -- SNIP --
        }
    }

    Ok(())
}


fn counters(mut player: UniqueViewMut<Player>) {
    player.pellet_counter = player.pellet_counter.saturating_sub(1);
}

fn spawn(mut entities: EntitiesViewMut, mut vm_power_pellets: ViewMut<PowerPellet>) {
    let width = screen_width();
    let height = screen_height();

    let pellet_spawn_rate = if vm_power_pellets.is_empty() {
        POWER_PELLET_SPAWN_RATE / 2
    } else {
        POWER_PELLET_SPAWN_RATE
    };

    if rand::gen_range(0, pellet_spawn_rate) == 0 {
        let x = rand::gen_range(0.0, width - INIT_SIZE);
        let y = rand::gen_range(0.0, height - INIT_SIZE);

        entities.add_entity(
            &mut vm_power_pellets,
            PowerPellet(Square {
                x,
                y,
                size: INIT_SIZE * 2.0,
            }),
        );
    }
}

The syntax to add entities is very similar to adding components.
But this time we need EntitiesViewMut.

With this change the Player is can now rest, stronger than ever.