Sokoban ggez

2020-01-06

~All ~Categories

Sokoban game

Basic ggez🔗

use ggez::event::{self, EventHandler};
use ggez::graphics::{self, Color};
use ggez::{conf, Context, ContextBuilder, GameError, GameResult};
use std::path;

// Game hold all the game state
struct Game {}

impl Game {
    pub fn new(_ctx: &mut Context) -> Game {
        Game {}
    }
}

impl EventHandler<GameError> for Game {
    fn update(&mut self, _ctx: &mut Context) -> GameResult {
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
        graphics::clear(ctx, Color::WHITE);
        graphics::present(ctx)
    }
}

fn main() {
    // create a game context and event loop
    let context_builder = ContextBuilder::new("babidiii_sokoban", "sokoban")
        .window_setup(conf::WindowSetup::default().title("Sokoban!"))
        .window_mode(conf::WindowMode::default().dimensions(1000.0, 600.0))
        .add_resource_path(path::PathBuf::from("./resources"));

    let (mut ctx, event_loop) = context_builder.build().expect("Could not create ggez game");

    // Create game state
    let game = Game::new(&mut ctx);

    event::run(ctx, event_loop, game)
}

ECS (Entity Component System)🔗

  • Components (data-only structs)[ex: Position, Renderable, Movement...]. No behabior
  • Entities (Multiples components)[ex: player with Position, Renderable & Movement; floor with Position & Movement...]
  • Systems (uses entities and components & contains behaviour logic based on that)

These are the three core concepts. Each entity is associated with some components. Those entities and components are processed by systems.
This way, you have your data (components) completely seperated from the behaviour (systems).
An entity just logically group components; so a velocity component can be applied to the Position component of the same entity.

ECS Crate: specs

[dependencies]
specs = { version = "0.16.1", features = ["specs-derive"] }

Specs book

+-----------+ 0..n       1 +--------+
| Component | ------------ | Entity |
+-----------+              +--------+
// Struct of an entity
struct Entity(u32, Generation);
// u32 is for the id
// Generation is used to check if the entity has been deleted

Data driven proramming

 +-------+ 
 | Force |
 +-------+ 
     |     +--------------------+        +----------+
     | --> | integration system | -----> | Velocity |
     |     +--------------------+        +----------+
 +------+
 | Mass |
 +------+

Hello world

use specs::{Component, VecStorage};

#[derive(Debug)]
struct Position {
    x: f32,
    y: f32,
}

impl Component for Position {
    type Storage = VecStorage<Self>;
}

Could be replace by using the specs-derive crate with custom #[derive]

[dependencies]
specs = { version = "0.16.1", features = ["specs-derive"] }
use specs::{Component, VecStorage};

// First way of doing

#[derive(Debug)]
struct Position {
    x: f32,
    y: f32,
}

impl Component for Position {
    type Storage = VecStorage<Self>;
}

// With drive specs-derive

#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Velocity {
    x: f32,
    y: f32,
}

Adding the world🔗

use specs::{Builder, Component, VecStorage, World, WorldExt};
// World is a struct coming from shred, an important dependency of Specs. 
// Whenever you call functions specific to Specs, you will need to import the WorldExt trait.

//...

fn main() {
    let mut world = World::new();
    world.register::<Position>();
    world.register::<Velocity>();

    let ball = world
        .create_entity()
        .with(Position { x: 4.0, y: 7.0 })
        .build();

}

The System🔗

struct HelloWorld;

impl<'a> System<'a> for HelloWorld {
    type SystemData = ReadStorage<'a, Position>;

    fn run(&mut self, position: Self::SystemData) {
        use specs::Join;
        for position in position.join() {
            println!("hello, {:?}", &position);
        }
    }
}


fn main() {
    // world creation and entities registering/creation
    
    let mut hello_world = HelloWorld;
    hello_world.run_now(&world);
    world.maintain(); 
    // If entities are created or deleted while a system is running, calling maintain will record the changes in its internal data structure.
}

Dispatcher🔗

    // create dispatcher with no dependancies
    let mut dispatcher = DispatcherBuilder::new()
        .with(HelloWorld, "hello_world", &[])
        .build();

If we create a new System like the following one which will update the position for entities that have a Position and Velocity Components:

struct UpdatePos;

impl<'a> System<'a> for UpdatePos {
    type SystemData = (ReadStorage<'a, Velocity>, WriteStorage<'a, Position>);

    fn run(&mut self, (vel, mut pos): Self::SystemData) {
        use specs::Join;
        for (vel, pos) in (&vel, &mut pos).join() {
            pos.x += vel.x * 0.05;
            pos.y += vel.y * 0.05;
        }
    }
}

Now if we add the new system to the dispatcher

fn main() {
    let mut world = World::new();
    world.register::<Position>();
    world.register::<Velocity>();

    // first entity with only Position component
    world
        .create_entity()
        .with(Position { x: 4.0, y: 7.0 })
        .build();

    // second entity with Position and Velocity 
    world
        .create_entity()
        .with(Position { x: 4.0, y: 7.0 })
        .with(Velocity { x: 0.1, y: 0.2 })
        .build();

    let mut dispatcher = DispatcherBuilder::new()
        .with(HelloWorld, "hello_world", &[])            
        .with(UpdatePos, "update_pos", &["hello_world"])    // update_pos depends on hello_world
        .with(HelloWorld, "hello_updated", &["update_pos"]) // hello_updated depends on update_pos
        .build();

    // hello_world.run_now(&world);
    dispatcher.dispatch(&mut world);
    // Dispatch all the systems with given resources and context and then run thread local systems.
    world.maintain();
}
Running `target/debug/specs-game`
hello, Position { x: 4.0, y: 7.0 }      // hello_world on entity 1
hello, Position { x: 4.0, y: 7.0 }      // hello_updated on entity 1
hello, Position { x: 4.0, y: 7.0 }      // hello_world on entity 2
hello, Position { x: 4.005, y: 7.001}   // hello_updated on entity 2

Resources🔗

  • Data which is shared between Systems

Let's create the resource

#[derive(Default)]
struct DeltaTime(f32);

We then need to insert it in our container

fn main() {
    let mut world = World::new();
    world.register::<Position>();
    world.register::<Velocity>();

    // Inserts a resource into this container
    world.insert(DeltaTime(0.05));

    // update the deltatime
    {
        let mut delta = world.write_resource::<DeltaTime>(); // Fetches a resource for writing.
        *delta = DeltaTime(0.04);
    }
    // ...

Then in our system we can access it using Read

impl<'a> System<'a> for UpdatePos {
    type SystemData = (
        Read<'a, DeltaTime>,
        ReadStorage<'a, Velocity>,
        WriteStorage<'a, Position>,
    );

    fn run(&mut self, data: Self::SystemData) {
        let (delta, vel, mut pos) = data;

        let delta = delta.0; // Deref -> coerce to &Deltatime

        for (vel, pos) in (&vel, &mut pos).join() {
            pos.x += vel.x * delta;
            pos.y += vel.y * delta;
        }
    }
}

SystemData🔗

Link

Setup🔗

use specs::prelude::*;
use specs::*;

#[derive(Default)]
struct Gravity;

#[derive(Debug, Component)]
#[storage(VecStorage)]
struct Velocity;

struct SimulationSystem;

impl<'a> System<'a> for SimulationSystem {
    type SystemData = (Read<'a, Gravity>, WriteStorage<'a, Velocity>);

    fn run(&mut self, (gra, mut vel): Self::SystemData) {}
}

fn main() {
    let mut world = World::new();

    // world.insert(Gravity);
    // world.register::<Velocity>();

    let mut dispatcher = DispatcherBuilder::new()
        .with(SimulationSystem, "simulation_sys", &[])
        .build();

    dispatcher.setup(&mut world); // will replace insert and register

    for _ in 0..5 {
        world.create_entity().with(Velocity).build();
    }

    dispatcher.dispatch(&mut world);
    world.maintain();
}

Join🔗

Basic

for (pos, vel) in (&mut pos_storage, &vel_storage).join() {
    *pos += *vel;
}

Basic with enitity

for (ent, pos, vel) in (&*entities, &mut pos_storage, &vel_storage).join() {
    println!("Processing entity: {:?}", ent);
    *pos += *vel;
}

Optional components

for (pos, vel, mass) in 
    (&mut pos_storage, &vel_storage, (&mut mass_storage).maybe()).join() {
    println!("Processing entity: {:?}", ent);
    *pos += *vel;

    if let Some(mass) = mass {
        let x = *vel / 300_000_000.0;
        let y = 1 - x * x;
        let y = y.sqrt();
        mass.current = mass.constant / y;
    }
}

Do not have a join of only MaybeJoins

Manual fetching with Storage::get() or Storage::get_mut()

for (target, damage) in (&target_storage, &damage_storage).join() {
  
    let target_health: Option<&mut Health> = health_storage.get_mut(target.ent);
    if let Some(target_health) = target_health {
      target_health.current -= damage.value;      
    }
}

Excluding components

for (ent, pos, vel, ()) in (
    &*entities,
    &mut pos_storage,
    &vel_storage,
    !&frozen_storage,
).join() {
    println!("Processing entity: {:?}", ent);
    *pos += *vel;
}

Parallel Join

fn run(&mut self, (vel, mut pos): Self::SystemData) {
    use specs::Join;
    // This loop runs sequentially on a single thread.
    for (vel, pos) in (&vel, &mut pos).join() {
        pos.x += vel.x * 0.05;
        pos.y += vel.y * 0.05;
    }
}
fn run(&mut self, (vel, mut pos): Self::SystemData) {
    use rayon::prelude::*;
    use specs::ParJoin;

    (&vel, &mut pos)
        .par_join()
        .for_each(|(vel, pos)| {
            pos.x += vel.x * 0.05;
            pos.y += vel.y * 0.05;
        });
}