rust-sokoban/src/main.rs

385 lines
11 KiB
Rust
Raw Normal View History

2020-07-23 15:42:35 -04:00
use ggez;
use ggez::event::{KeyCode, KeyMods};
2020-07-23 11:55:36 -04:00
use ggez::graphics;
use ggez::graphics::DrawParam;
use ggez::graphics::Image;
use ggez::nalgebra as na;
use ggez::{conf, event, Context, GameResult};
2020-07-23 16:35:54 -04:00
use specs::world::Index;
use specs::Entities;
use specs::NullStorage;
use specs::WriteStorage;
2020-07-23 11:55:36 -04:00
use specs::{
2020-07-23 16:35:54 -04:00
join::Join, Builder, Component, ReadStorage, RunNow, System, VecStorage, World, WorldExt, Write,
2020-07-23 11:55:36 -04:00
};
2020-07-23 16:35:54 -04:00
use std::collections::HashMap;
2020-07-23 11:55:36 -04:00
use std::path;
const TILE_WIDTH: f32 = 32.0;
2020-07-23 16:35:54 -04:00
const MAP_WIDTH: u8 = 8;
const MAP_HEIGHT: u8 = 9;
2020-07-23 11:55:36 -04:00
// Components
#[derive(Debug, Component, Clone, Copy)]
#[storage(VecStorage)]
pub struct Position {
x: u8,
y: u8,
z: u8,
}
2020-07-23 14:01:14 -04:00
impl Position {
pub fn new(x: u8, y: u8) -> Self {
2020-07-23 15:42:35 -04:00
Position { x, y, z: 0 }
2020-07-23 14:01:14 -04:00
}
}
2020-07-23 11:55:36 -04:00
#[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 {}
2020-07-23 16:35:54 -04:00
#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Movable;
#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Immovable;
2020-07-23 15:42:35 -04:00
// Resources
#[derive(Default)]
pub struct InputQueue {
pub keys_pressed: Vec<KeyCode>,
}
2020-07-23 11:55:36 -04:00
// 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::<Vec<_>>();
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");
}
}
2020-07-23 15:42:35 -04:00
pub struct InputSystem {}
impl<'a> System<'a> for InputSystem {
// Data
type SystemData = (
Write<'a, InputQueue>,
2020-07-23 16:35:54 -04:00
Entities<'a>,
2020-07-23 15:42:35 -04:00
WriteStorage<'a, Position>,
ReadStorage<'a, Player>,
2020-07-23 16:35:54 -04:00
ReadStorage<'a, Movable>,
ReadStorage<'a, Immovable>,
2020-07-23 15:42:35 -04:00
);
fn run(&mut self, data: Self::SystemData) {
2020-07-23 16:35:54 -04:00
let (mut input_queue, entities, mut positions, players, movables, immovables) = data;
let mut to_move = Vec::new();
2020-07-23 15:42:35 -04:00
2020-07-23 16:35:54 -04:00
for (position, _player) in (&positions, &players).join() {
2020-07-23 15:42:35 -04:00
// Get the first key pressed
if let Some(key) = input_queue.keys_pressed.pop() {
2020-07-23 16:35:54 -04:00
// get all the movables and immovables
let mov: HashMap<(u8, u8), Index> = (&entities, &movables, &positions)
.join()
.map(|t| ((t.2.x, t.2.y), t.0.id()))
.collect();
let immov: HashMap<(u8, u8), Index> = (&entities, &immovables, &positions)
.join()
.map(|t| ((t.2.x, t.2.y), t.0.id()))
.collect();
// Now iterate through current position to the end of the map
// on the correct axis and check what needs to move.
let (start, end, is_x) = match key {
KeyCode::Up => (position.y, 0, false),
KeyCode::Down => (position.y, MAP_HEIGHT, false),
KeyCode::Left => (position.x, 0, true),
KeyCode::Right => (position.x, MAP_WIDTH, true),
_ => continue,
};
let range: Vec<_> = if start < end {
(start..=end).collect()
} else {
(end..=start).rev().collect()
};
for x_or_y in range {
let pos = if is_x {
(x_or_y, position.y)
} else {
(position.x, x_or_y)
};
// find a movable
// if it exists, we try to move it and continue
// if it doesn't exist, we continue and try to find an immovable instead
match mov.get(&pos) {
Some(id) => to_move.push((key, id.clone())),
None => {
// find an immovable
// if it exists, we need to stop and not move anything
// if it doesn't exist, we stop because we found a gap
match immov.get(&pos) {
Some(_) => to_move.clear(),
None => break,
}
}
}
}
}
}
// Now actually move what needs to be moved
for (key, id) in to_move {
let position = positions.get_mut(entities.entity(id));
if let Some(position) = position {
2020-07-23 15:42:35 -04:00
match key {
KeyCode::Up => position.y -= 1,
KeyCode::Down => position.y += 1,
KeyCode::Left => position.x -= 1,
KeyCode::Right => position.x += 1,
_ => (),
}
}
}
}
}
2020-07-23 11:55:36 -04:00
// All the game state
struct Game {
world: World,
}
impl event::EventHandler for Game {
fn update(&mut self, _context: &mut Context) -> GameResult {
2020-07-23 15:42:35 -04:00
// Run input system
{
let mut is = InputSystem {};
is.run_now(&self.world);
}
2020-07-23 11:55:36 -04:00
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
// Render gaem entities
{
let mut rs = RenderingSystem { context };
rs.run_now(&self.world);
}
Ok(())
}
2020-07-23 15:42:35 -04:00
fn key_down_event(
&mut self,
_context: &mut Context,
keycode: KeyCode,
_keymod: KeyMods,
_repeat: bool,
) {
println!("Key pressed: {:?}", keycode);
let mut input_queue = self.world.write_resource::<InputQueue>();
input_queue.keys_pressed.push(keycode);
}
2020-07-23 11:55:36 -04:00
}
pub fn register_components(world: &mut World) {
world.register::<Position>();
world.register::<Renderable>();
world.register::<Player>();
world.register::<Wall>();
world.register::<Box>();
world.register::<BoxSpot>();
2020-07-23 16:35:54 -04:00
world.register::<Movable>();
world.register::<Immovable>();
2020-07-23 11:55:36 -04:00
}
2020-07-23 15:42:35 -04:00
pub fn register_resources(world: &mut World) {
world.insert(InputQueue::default())
}
2020-07-23 11:55:36 -04:00
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 {})
2020-07-23 16:35:54 -04:00
.with(Immovable)
2020-07-23 11:55:36 -04:00
.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(),
})
2020-07-23 16:35:54 -04:00
.with(Box {})
.with(Movable)
2020-07-23 11:55:36 -04:00
.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 {})
2020-07-23 16:35:54 -04:00
.with(Movable)
2020-07-23 11:55:36 -04:00
.build();
}
2020-07-23 14:01:14 -04:00
pub fn load_map(world: &mut World, map_string: String) {
// read all lines
2020-07-23 15:42:35 -04:00
let rows: Vec<&str> = map_string.trim().split('\n').map(|x| x.trim()).collect();
2020-07-23 14:01:14 -04:00
for (y, row) in rows.iter().enumerate() {
let columns: Vec<&str> = row.split(' ').collect();
for (x, column) in columns.iter().enumerate() {
// Create the position at which to create something on the map
let position = Position::new(x as u8, y as u8);
// Figure out which object to create
match *column {
"." => create_floor(world, position),
"W" => {
create_floor(world, position);
create_wall(world, position);
}
"P" => {
create_floor(world, position);
create_player(world, position);
}
"B" => {
create_floor(world, position);
create_box(world, position);
}
"S" => {
create_floor(world, position);
create_box_spot(world, position);
2020-07-23 15:42:35 -04:00
}
2020-07-23 14:01:14 -04:00
"N" => (),
c => panic!("unrecognized map item {}", c),
}
2020-07-23 13:24:12 -04:00
}
2020-07-23 14:01:14 -04:00
}
}
pub fn initialize_level(world: &mut World) {
const MAP: &str = "
N N W W W W W W
W W W . . . . W
W . . . B . . W
W . . . . . . W
W . P . . . . W
W . . . . . . W
W . . S . . . W
W . . . . . . W
W W W W W W W W
";
load_map(world, MAP.to_string());
2020-07-23 11:55:36 -04:00
}
pub fn main() -> GameResult {
let mut world = World::new();
register_components(&mut world);
2020-07-23 15:42:35 -04:00
register_resources(&mut world);
2020-07-23 11:55:36 -04:00
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)
}