Browse Source

Chapter 4: map generation and VI keys.

Herbert Wolverson 4 years ago
parent
commit
67f9fc836d

+ 9 - 0
Cargo.lock

@@ -142,6 +142,15 @@ dependencies = [
  "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "chapter-04-newmap"
+version = "0.1.0"
+dependencies = [
+ "rltk 0.2.5 (git+https://github.com/thebracket/rltk_rs)",
+ "specs 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "cloudabi"
 version = "0.0.3"

+ 1 - 1
Cargo.toml

@@ -12,4 +12,4 @@ specs = "0.15.0"
 specs-derive = "0.4.0"
 
 [workspace]
-members = [ "chapter-01-hellorust", "chapter-02-helloecs", "chapter-03-walkmap" ]
+members = [ "chapter-01-hellorust", "chapter-02-helloecs", "chapter-03-walkmap", "chapter-04-newmap" ]

+ 1 - 0
book/src/SUMMARY.md

@@ -3,3 +3,4 @@
 - [Chapter 1 - Hello Rust](./chapter_1.md)
 - [Chapter 2 - Entities & Components](./chapter_2.md)
 - [Chapter 3 - Walking A Map](./chapter_3.md)
+- [Chapter 4 - A More Interesting Map](./chapter_4.md)

+ 12 - 0
chapter-04-newmap/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "chapter-04-newmap"
+version = "0.1.0"
+authors = ["Herbert Wolverson <herberticus@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+rltk = { git = "https://github.com/thebracket/rltk_rs" }
+specs = "0.15.0"
+specs-derive = "0.4.0"

+ 21 - 0
chapter-04-newmap/src/components.rs

@@ -0,0 +1,21 @@
+extern crate specs;
+use specs::prelude::*;
+extern crate specs_derive;
+extern crate rltk;
+use rltk::{RGB};
+
+#[derive(Component)]
+pub struct Position {
+    pub x: i32,
+    pub y: i32,
+}
+
+#[derive(Component)]
+pub struct Renderable {
+    pub glyph: u8,
+    pub fg: RGB,
+    pub bg: RGB,
+}
+ 
+#[derive(Component, Debug)]
+pub struct Player {}

+ 67 - 0
chapter-04-newmap/src/main.rs

@@ -0,0 +1,67 @@
+extern crate rltk;
+use rltk::{Console, GameState, Rltk, RGB};
+extern crate specs;
+use specs::prelude::*;
+#[macro_use]
+extern crate specs_derive;
+mod components;
+pub use components::*;
+mod map;
+pub use map::*;
+mod player;
+use player::*;
+mod rect;
+pub use rect::Rect;
+
+pub struct State {
+    pub ecs: World,
+    pub systems: Dispatcher<'static, 'static>
+}
+
+impl GameState for State {
+    fn tick(&mut self, ctx : &mut Rltk) {
+        ctx.cls();
+
+        player_input(self, ctx);
+        self.systems.dispatch(&self.ecs);
+
+        let map = self.ecs.fetch::<Vec<TileType>>();
+        draw_map(&map, ctx);
+
+        let positions = self.ecs.read_storage::<Position>();
+        let renderables = self.ecs.read_storage::<Renderable>();
+
+        for (pos, render) in (&positions, &renderables).join() {
+            ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph);
+        }
+    }
+}
+
+fn main() {
+    let context = Rltk::init_simple8x8(80, 50, "Hello Rust World", "../resources");
+    let mut gs = State {
+        ecs: World::new(),
+        systems : DispatcherBuilder::new()
+            .build()
+    };
+    gs.ecs.register::<Position>();
+    gs.ecs.register::<Renderable>();
+    gs.ecs.register::<Player>();
+
+    let (rooms, map) = new_map_rooms_and_corridors();
+    gs.ecs.insert(map);
+    let (player_x, player_y) = rooms[0].center();
+
+    gs.ecs
+        .create_entity()
+        .with(Position { x: player_x, y: player_y })
+        .with(Renderable {
+            glyph: rltk::to_cp437('@'),
+            fg: RGB::named(rltk::YELLOW),
+            bg: RGB::named(rltk::BLACK),
+        })
+        .with(Player{})
+        .build();
+
+    rltk::main_loop(context, gs);
+}

+ 137 - 0
chapter-04-newmap/src/map.rs

@@ -0,0 +1,137 @@
+extern crate rltk;
+use rltk::{ RGB, Rltk, Console, RandomNumberGenerator };
+use super::{Rect};
+use std::cmp::{max, min};
+
+#[derive(PartialEq, Copy, Clone)]
+pub enum TileType {
+    Wall, Floor
+}
+
+pub fn xy_idx(x: i32, y: i32) -> usize {
+    (y as usize * 80) + x as usize
+}
+
+/// Makes a map with solid boundaries and 400 randomly placed walls. No guarantees that it won't
+/// look awful.
+pub fn new_map_test() -> Vec<TileType> {
+    let mut map = vec![TileType::Floor; 80*50];
+
+    // Make the boundaries walls
+    for x in 0..80 {
+        map[xy_idx(x, 0)] = TileType::Wall;
+        map[xy_idx(x, 49)] = TileType::Wall;
+    }
+    for y in 0..50 {
+        map[xy_idx(0, y)] = TileType::Wall;
+        map[xy_idx(79, y)] = TileType::Wall;
+    }
+
+    // Now we'll randomly splat a bunch of walls. It won't be pretty, but it's a decent illustration.
+    // First, obtain the thread-local RNG:
+    let mut rng = rltk::RandomNumberGenerator::new();
+
+    for _i in 0..400 {
+        let x = rng.roll_dice(1, 79);
+        let y = rng.roll_dice(1, 49);
+        let idx = xy_idx(x, y);
+        if idx != xy_idx(40, 25) {
+            map[idx] = TileType::Wall;
+        }
+    }
+
+    map
+}
+
+fn apply_room_to_map(room : &Rect, map: &mut [TileType]) {
+    for y in room.y1 +1 ..= room.y2 {
+        for x in room.x1 + 1 ..= room.x2 {
+            map[xy_idx(x, y)] = TileType::Floor;
+        }
+    }
+}
+
+fn apply_horizontal_tunnel(map: &mut [TileType], x1:i32, x2:i32, y:i32) {
+    for x in min(x1,x2) ..= max(x1,x2) {
+        let idx = xy_idx(x, y);
+        if idx > 0 && idx < 80*50 {
+            map[idx as usize] = TileType::Floor;
+        }
+    }
+}
+
+fn apply_vertical_tunnel(map: &mut [TileType], y1:i32, y2:i32, x:i32) {
+    for y in min(y1,y2) ..= max(y1,y2) {
+        let idx = xy_idx(x, y);
+        if idx > 0 && idx < 80*50 {
+            map[idx as usize] = TileType::Floor;
+        }
+    }
+}
+
+/// Makes a new map using the algorithm from http://rogueliketutorials.com/tutorials/tcod/part-3/
+/// This gives a handful of random rooms and corridors joining them together.
+pub fn new_map_rooms_and_corridors() -> (Vec<Rect>, Vec<TileType>) {
+    let mut map = vec![TileType::Wall; 80*50];
+
+    let mut rooms : Vec<Rect> = Vec::new();
+    const MAX_ROOMS : i32 = 30;
+    const MIN_SIZE : i32 = 6;
+    const MAX_SIZE : i32 = 10;
+
+    let mut rng = RandomNumberGenerator::new();
+
+    for _i in 0..MAX_ROOMS {
+        let w = rng.range(MIN_SIZE, MAX_SIZE);
+        let h = rng.range(MIN_SIZE, MAX_SIZE);
+        let x = rng.roll_dice(1, 80 - w - 1) - 1;
+        let y = rng.roll_dice(1, 50 - h - 1) - 1;
+        let new_room = Rect::new(x, y, w, h);
+        let mut ok = true;
+        for other_room in rooms.iter() {
+            if new_room.intersect(other_room) { ok = false }
+        }
+        if ok {
+            apply_room_to_map(&new_room, &mut map);
+
+            if !rooms.is_empty() {
+                let (new_x, new_y) = new_room.center();
+                let (prev_x, prev_y) = rooms[rooms.len()-1].center();
+                if rng.range(0,1) == 1 {
+                    apply_horizontal_tunnel(&mut map, prev_x, new_x, prev_y);
+                    apply_vertical_tunnel(&mut map, prev_y, new_y, new_x);
+                } else {
+                    apply_vertical_tunnel(&mut map, prev_y, new_y, prev_x);
+                    apply_horizontal_tunnel(&mut map, prev_x, new_x, new_y);
+                }
+            }
+
+            rooms.push(new_room);
+        }
+    }
+
+    (rooms, map)
+}
+
+pub fn draw_map(map: &[TileType], ctx : &mut Rltk) {
+    let mut y = 0;
+    let mut x = 0;
+    for tile in map.iter() {
+        // Render a tile depending upon the tile type
+        match tile {
+            TileType::Floor => {
+                ctx.set(x, y, RGB::from_f32(0.5, 0.5, 0.5), RGB::from_f32(0., 0., 0.), rltk::to_cp437('.'));
+            }
+            TileType::Wall => {
+                ctx.set(x, y, RGB::from_f32(0.0, 1.0, 0.0), RGB::from_f32(0., 0., 0.), rltk::to_cp437('#'));
+            }
+        }
+
+        // Move the coordinates
+        x += 1;
+        if x > 79 {
+            x = 0;
+            y += 1;
+        }
+    }
+}

+ 46 - 0
chapter-04-newmap/src/player.rs

@@ -0,0 +1,46 @@
+extern crate rltk;
+use rltk::{VirtualKeyCode, Rltk};
+extern crate specs;
+use specs::prelude::*;
+use super::{Position, Player, TileType, xy_idx, State};
+
+pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
+    let mut positions = ecs.write_storage::<Position>();
+    let mut players = ecs.write_storage::<Player>();
+    let map = ecs.fetch::<Vec<TileType>>();
+
+    for (_player, pos) in (&mut players, &mut positions).join() {
+        let destination_idx = xy_idx(pos.x + delta_x, pos.y + delta_y);
+        if map[destination_idx] != TileType::Wall {
+            pos.x += delta_x;
+            pos.y += delta_y;
+
+            if pos.x < 0 { pos.x = 0; }
+            if pos.x > 79 { pos.y = 79; }
+            if pos.y < 0 { pos.y = 0; }
+            if pos.y > 49 { pos.y = 49; }
+        }
+    }
+}
+
+pub fn player_input(gs: &mut State, ctx: &mut Rltk) {
+    // Player movement
+    match ctx.key {
+        None => {} // Nothing happened
+        Some(key) => match key {
+            VirtualKeyCode::Left => try_move_player(-1, 0, &mut gs.ecs),
+            VirtualKeyCode::Numpad4 => try_move_player(-1, 0, &mut gs.ecs),
+            VirtualKeyCode::H => try_move_player(-1, 0, &mut gs.ecs),
+            VirtualKeyCode::Right => try_move_player(1, 0, &mut gs.ecs),
+            VirtualKeyCode::Numpad6 => try_move_player(1, 0, &mut gs.ecs),
+            VirtualKeyCode::L => try_move_player(1, 0, &mut gs.ecs),
+            VirtualKeyCode::Up => try_move_player(0, -1, &mut gs.ecs),
+            VirtualKeyCode::Numpad8 => try_move_player(0, -1, &mut gs.ecs),
+            VirtualKeyCode::K => try_move_player(0, -1, &mut gs.ecs),
+            VirtualKeyCode::Down => try_move_player(0, 1, &mut gs.ecs),
+            VirtualKeyCode::Numpad2 => try_move_player(0, 1, &mut gs.ecs),
+            VirtualKeyCode::J => try_move_player(0, 1, &mut gs.ecs),
+            _ => {}
+        },
+    }
+}

+ 21 - 0
chapter-04-newmap/src/rect.rs

@@ -0,0 +1,21 @@
+pub struct Rect {
+    pub x1 : i32,
+    pub x2 : i32,
+    pub y1 : i32,
+    pub y2 : i32
+}
+
+impl Rect {
+    pub fn new(x:i32, y: i32, w:i32, h:i32) -> Rect {
+        Rect{x1:x, y1:y, x2:x+w, y2:y+h}
+    }
+
+    // Returns true if this overlaps with other
+    pub fn intersect(&self, other:&Rect) -> bool {
+        self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
+    }
+
+    pub fn center(&self) -> (i32, i32) {
+        ((self.x1 + self.x2)/2, (self.y1 + self.y2)/2)
+    }
+}