Commit c4af6ec5 authored by Mathieu Nivoliez's avatar Mathieu Nivoliez

Add lecture "A more interesting map". Now, we have an auto generate map with...

Add lecture "A more interesting map". Now, we have an auto generate map with rooms and corridors. Also the code is now divided into several files or modules.
parent 01a36352
Pipeline #777 passed with stage
in 7 minutes and 41 seconds
......@@ -8,3 +8,4 @@ Little Rogue Like made in Rust using the [guide](http://bfnightly.bracketproduct
- [Section 1](http://bfnightly.bracketproductions.com/rustbook/chapter_1.html)
- [Entities and component](http://bfnightly.bracketproductions.com/rustbook/chapter_2.html)
- [Walking a Map](http://bfnightly.bracketproductions.com/rustbook/chapter_3.html)
- [A more interesting map](http://bfnightly.bracketproductions.com/rustbook/chapter_4.html)
use specs::prelude::*;
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 {}
\ No newline at end of file
// Only useful if compile to wasm
rltk::add_wasm_support!();
use rltk::{Console, GameState, Rltk, VirtualKeyCode, RGB};
use rltk::{Console, GameState, Rltk, RGB};
use specs::prelude::*;
use std::cmp::{max, min};
#[macro_use]
extern crate specs_derive;
// Component
#[derive(Component)]
struct Position {
x: i32,
y: i32,
}
#[derive(Component)]
struct Renderable {
glyph: u8,
fg: RGB,
bg: RGB,
}
#[derive(Component, Debug)]
struct Player;
#[derive(PartialEq, Copy, Clone)]
enum TileType {
Wall,
Floor,
}
// Functions
pub fn xy_idx(x: i32, y: i32) -> usize {
(y as usize * 80) + x as usize
}
fn new_map() -> 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 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 TileType
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('#'),
);
}
}
x += 1;
if x > 79 {
x = 0;
y += 1;
}
}
}
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 = min(79, max(0, pos.x + delta_x));
pos.y = min(49, max(0, pos.y + delta_y));
}
}
}
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::Right => try_move_player(1, 0, &mut gs.ecs),
VirtualKeyCode::Up => try_move_player(0, -1, &mut gs.ecs),
VirtualKeyCode::Down => try_move_player(0, 1, &mut gs.ecs),
_ => {}
},
}
}
mod components;
pub use components::*;
mod map;
pub use map::*;
mod player;
pub use player::*;
mod rect;
pub use rect::Rect;
// Main State
struct State {
ecs: World,
pub struct State {
pub ecs: World,
}
impl State {
fn run_systems(&mut self) {}
fn run_systems(&mut self) {
self.ecs.maintain();
}
}
impl GameState for State {
......@@ -150,7 +37,7 @@ impl GameState for State {
let renderables = self.ecs.read_storage::<Renderable>();
let map = self.ecs.fetch::<Vec<TileType>>();
draw_map(&map, ctx);
map::draw_map(&map, ctx);
for (pos, render) in (&positions, &renderables).join() {
ctx.set(pos.x, pos.y, render.fg, render.bg, render.glyph);
......@@ -171,20 +58,23 @@ fn main() {
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();
// Create an entity
gs.ecs
.create_entity()
.with(Position { x: 40, y: 25 })
.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)
.with(Player {})
.build();
gs.ecs.insert(new_map());
// Run the game
rltk::main_loop(context, gs);
}
use super::Rect;
use rltk::{Console, RandomNumberGenerator, Rltk, RGB};
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
}
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 _ 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,2) == 1 {
apply_horizontal_tunnel(&mut map, prev_x, new_x, prev_y);
apply_vertical_tunnel(&mut map, prev_y, new_y, prev_x);
} else {
apply_vertical_tunnel(&mut map, prev_y, new_y, prev_x);
apply_horizontal_tunnel(&mut map, prev_x, new_x, prev_y);
}
}
rooms.push(new_room);
}
}
(rooms, map)
}
fn apply_room_to_map(room: &Rect, map: &mut [TileType]) {
for y in room.y1..=room.y2 {
for x in room.x1..=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;
}
}
}
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 TileType
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('#'),
);
}
}
x += 1;
if x > 79 {
x = 0;
y += 1;
}
}
}
use super::{xy_idx, Player, Position, State, TileType};
use rltk::{Rltk, VirtualKeyCode};
use specs::prelude::*;
use std::cmp::{max, min};
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 = min(79, max(0, pos.x + delta_x));
pos.y = min(49, max(0, pos.y + delta_y));
}
}
}
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),
_ => {}
},
}
}
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,
x2: x + w,
y1: y,
y2: y + h,
}
}
pub fn intersect(&self, other: &Rect) -> bool {
self.x1 <= other.x1 && self.x2 >= other.x2 && self.y1 <= other.y1 && self.y2 >= other.y2
}
pub fn center(&self) -> (i32, i32) {
((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment