diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3a4b628 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rust-sokoban" +version = "0.1.0" +authors = ["Timothy Warren "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ggez = "0.5.1" +specs = { version="0.15.0", features=["specs-derive"] } \ No newline at end of file diff --git a/resources/images/box.png b/resources/images/box.png new file mode 100644 index 0000000..38f61e4 Binary files /dev/null and b/resources/images/box.png differ diff --git a/resources/images/box_spot.png b/resources/images/box_spot.png new file mode 100644 index 0000000..c5f659e Binary files /dev/null and b/resources/images/box_spot.png differ diff --git a/resources/images/floor.png b/resources/images/floor.png new file mode 100644 index 0000000..ee2711c Binary files /dev/null and b/resources/images/floor.png differ diff --git a/resources/images/player.png b/resources/images/player.png new file mode 100644 index 0000000..a8f3b5b Binary files /dev/null and b/resources/images/player.png differ diff --git a/resources/images/wall.png b/resources/images/wall.png new file mode 100644 index 0000000..8a0e80a Binary files /dev/null and b/resources/images/wall.png differ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a596e43 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,212 @@ +use ggez::graphics; +use ggez::graphics::DrawParam; +use ggez::graphics::Image; +use ggez::nalgebra as na; +use ggez::{conf, event, Context, GameResult}; +use specs::{ + join::Join, Builder, Component, ReadStorage, RunNow, System, VecStorage, World, WorldExt, +}; + +use std::path; + +const TILE_WIDTH: f32 = 32.0; + +// Components +#[derive(Debug, Component, Clone, Copy)] +#[storage(VecStorage)] +pub struct Position { + x: u8, + y: u8, + z: u8, +} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Renderable { + path: String, +} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Wall {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Player {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct Box {} + +#[derive(Component)] +#[storage(VecStorage)] +pub struct BoxSpot {} + +// Systems +pub struct RenderingSystem<'a> { + context: &'a mut Context, +} + +impl<'a> System<'a> for RenderingSystem<'a> { + type SystemData = (ReadStorage<'a, Position>, ReadStorage<'a, Renderable>); + + fn run(&mut self, data: Self::SystemData) { + let (positions, renderables) = data; + + // Clear the screen/set the background + graphics::clear(self.context, graphics::Color::new(0.95, 0.95, 0.95, 1.0)); + + // Get all the renderables with their positions and sort by the position z + // This will allow us to have entities layered visually. + let mut rendering_data = (&positions, &renderables).join().collect::>(); + rendering_data.sort_by(|&a, &b| a.0.z.partial_cmp(&b.0.z).expect("expected comparison")); + + // Iterate through all paris of positions & renderables, load the image + // and draw it at the specified position. + for (position, renderable) in rendering_data.iter() { + // Load the image + let image = Image::new(self.context, renderable.path.clone()).expect("expected image"); + let x = position.x as f32 * TILE_WIDTH; + let y = position.y as f32 * TILE_WIDTH; + + // draw + let draw_params = DrawParam::new().dest(na::Point2::new(x, y)); + graphics::draw(self.context, &image, draw_params).expect("expected render"); + } + + // Finally, present the context, this will actually display everything + // on the screen. + graphics::present(self.context).expect("expected to present"); + } +} + +// All the game state +struct Game { + world: World, +} + +impl event::EventHandler for Game { + fn update(&mut self, _context: &mut Context) -> GameResult { + Ok(()) + } + + fn draw(&mut self, context: &mut Context) -> GameResult { + // Render gaem entities + { + let mut rs = RenderingSystem { context }; + rs.run_now(&self.world); + } + + Ok(()) + } +} + +pub fn register_components(world: &mut World) { + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); + world.register::(); +} + +pub fn create_wall(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/wall.png".to_string(), + }) + .with(Wall {}) + .build(); +} + +pub fn create_floor(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 5, ..position }) + .with(Renderable { + path: "/images/floor.png".to_string(), + }) + .build(); +} + +pub fn create_box(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/box.png".to_string(), + }) + .build(); +} + +pub fn create_box_spot(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 9, ..position }) + .with(Renderable { + path: "/images/box_spot.png".to_string(), + }) + .with(BoxSpot {}) + .build(); +} + +pub fn create_player(world: &mut World, position: Position) { + world + .create_entity() + .with(Position { z: 10, ..position }) + .with(Renderable { + path: "/images/player.png".to_string(), + }) + .with(Player {}) + .build(); +} + +pub fn initialize_level(world: &mut World) { + create_player( + world, + Position { + x: 0, + y: 0, + z: 0, // we will get the z from the factory functions + }, + ); + create_wall( + world, + Position { + x: 1, + y: 0, + z: 0, // we will get the z from the factory functions + }, + ); + create_box( + world, + Position { + x: 2, + y: 0, + z: 0, // we will get the z from the factory functions + }, + ); +} + + +pub fn main() -> GameResult { + let mut world = World::new(); + register_components(&mut world); + initialize_level(&mut world); + + // Create a game context and event loop + let context_builder = ggez::ContextBuilder::new("rust_sokoban", "Timothy J. Warren") + .window_setup(conf::WindowSetup::default().title("Rust Sokoban!")) + .window_mode(conf::WindowMode::default().dimensions(800.0, 600.0)) + .add_resource_path(path::PathBuf::from("./resources")); + + let (context, event_loop) = &mut context_builder.build()?; + + // Create the game state + let game = &mut Game { world }; + + // Run the main event loop + event::run(context, event_loop, game) +}