Parcourir la source

First chapter 24 refactor.

Herbert Wolverson il y a 4 ans
Parent
commit
6b8d7b3996

+ 32 - 0
Cargo.lock

@@ -417,10 +417,29 @@ dependencies = [
 [[package]]
 name = "chapter-24-bsproom-dungeons"
 version = "0.1.0"
+dependencies = [
+ "rltk 0.3.1 (git+https://github.com/thebracket/rltk_rs)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "chapter-24-map-testing"
 version = "0.1.0"
+dependencies = [
+ "generator 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rltk 0.3.1 (git+https://github.com/thebracket/rltk_rs)",
+ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "specs-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "cloudabi"
@@ -647,6 +666,18 @@ name = "futures"
 version = "0.1.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "generator"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.1.12"
@@ -2012,6 +2043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
+"checksum generator 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "872276f48cbbcb137d8a3ab9428ec050b8afb2745c00556113c7b7516cc0915c"
 "checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571"
 "checksum gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39a23d5e872a275135d66895d954269cf5e8661d234eb1c2480f4ce0d586acbd"
 "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a"

+ 137 - 0
book/src/chapter_24.md

@@ -10,6 +10,143 @@
 
 ---
 
+As we're diving into generating new and interesting maps, it would be helpful to provide a way to *see* what the algorithms are doing. This chapter will build a test harness to accomplish this, and extend the `SimpleMapBuilder` from the previous chapter to support it. This is going to be a relatively large task, and we'll learn some new techniques along the way!
+
+## Cleaning up map creation - Do Not Repeat Yourself
+
+In `main.rs`, we essentially have the same code three times. When the program starts, we insert a map into the world. When we change level, or finish the game - we do the same. The last two have different semantics (since we're updating the world rather than inserting for the first time) - but it's basically redundant repetition.
+
+We'll start by changing the first one to insert *placeholder* values rather than the actual values we intend to use. This way, the `World` has the slots for the data - it just isn't all that useful yet. Here's a version with the old code commented out:
+
+```rust
+gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
+
+gs.ecs.insert(Map::new(1));
+gs.ecs.insert(Point::new(0, 0));
+gs.ecs.insert(rltk::RandomNumberGenerator::new());
+
+/*let mut builder = map_builders::random_builder(1);
+builder.build_map();
+let player_start = builder.get_starting_position();
+let map = builder.get_map();
+let (player_x, player_y) = (player_start.x, player_start.y);
+builder.spawn_entities(&mut gs.ecs);
+gs.ecs.insert(map);
+gs.ecs.insert(Point::new(player_x, player_y));*/
+
+let player_entity = spawner::player(&mut gs.ecs, 0, 0);
+gs.ecs.insert(player_entity);
+```
+
+So instead of building the map, we put a placeholder into the `World` resources. That's obviously not very useful for actually starting the game, so we also need a function to do the actual building and update the resources. Not entirely coincidentally, that function is the same as the other two places from which we currently update the map! In other words, we can roll those into this function, too. So in the implementation of `State`, we add:
+
+```rust
+fn generate_world_map(&mut self, new_depth : i32) {
+    let mut builder = map_builders::random_builder(new_depth);
+    builder.build_map();
+    let player_start;
+    {
+        let mut worldmap_resource = self.ecs.write_resource::<Map>();
+        *worldmap_resource = builder.get_map();
+        player_start = builder.get_starting_position();
+    }
+
+    // Spawn bad guys
+    builder.spawn_entities(&mut self.ecs);
+
+    // Place the player and update resources
+    let (player_x, player_y) = (player_start.x, player_start.y);
+    let mut player_position = self.ecs.write_resource::<Point>();
+    *player_position = Point::new(player_x, player_y);
+    let mut position_components = self.ecs.write_storage::<Position>();
+    let player_entity = self.ecs.fetch::<Entity>();
+    let player_pos_comp = position_components.get_mut(*player_entity);
+    if let Some(player_pos_comp) = player_pos_comp {
+        player_pos_comp.x = player_x;
+        player_pos_comp.y = player_y;
+    }
+
+    // Mark the player's visibility as dirty
+    let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
+    let vs = viewshed_components.get_mut(*player_entity);
+    if let Some(vs) = vs {
+        vs.dirty = true;
+    } 
+}
+```
+
+Now we can get rid of the commented out code, and simplify our first call quite a bit:
+
+```rust
+gs.ecs.insert(Map::new(1));
+gs.ecs.insert(Point::new(0, 0));
+gs.ecs.insert(rltk::RandomNumberGenerator::new());
+let player_entity = spawner::player(&mut gs.ecs, 0, 0);
+gs.ecs.insert(player_entity);
+gs.ecs.insert(RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame });
+gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to Rusty Roguelike".to_string()] });
+gs.ecs.insert(particle_system::ParticleBuilder::new());
+gs.ecs.insert(rex_assets::RexAssets::new());
+
+gs.generate_world_map(1);
+```
+
+We can also go to the various parts of the code that call the same code we just added to `generate_world_map` and greatly simplify them by using the new function. We can replace `goto_next_level` with:
+
+```rust
+fn goto_next_level(&mut self) {
+    // Delete entities that aren't the player or his/her equipment
+    let to_delete = self.entities_to_remove_on_level_change();
+    for target in to_delete {
+        self.ecs.delete_entity(target).expect("Unable to delete entity");
+    }
+
+    // Build a new map and place the player
+    let current_depth;
+    {
+        let worldmap_resource = self.ecs.fetch::<Map>();
+        current_depth = worldmap_resource.depth;
+    }
+    self.generate_world_map(current_depth + 1);
+
+    // Notify the player and give them some health
+    let player_entity = self.ecs.fetch::<Entity>();
+    let mut gamelog = self.ecs.fetch_mut::<gamelog::GameLog>();
+    gamelog.entries.insert(0, "You descend to the next level, and take a moment to heal.".to_string());
+    let mut player_health_store = self.ecs.write_storage::<CombatStats>();
+    let player_health = player_health_store.get_mut(*player_entity);
+    if let Some(player_health) = player_health {
+        player_health.hp = i32::max(player_health.hp, player_health.max_hp / 2);
+    }
+}
+```
+
+Likewise, we can clean up `game_over_cleanup`:
+
+```rust
+fn game_over_cleanup(&mut self) {
+    // Delete everything
+    let mut to_delete = Vec::new();
+    for e in self.ecs.entities().join() {
+        to_delete.push(e);
+    }
+    for del in to_delete.iter() {
+        self.ecs.delete_entity(*del).expect("Deletion failed");
+    }
+
+    // Spawn a new player
+    {
+        let player_entity = spawner::player(&mut self.ecs, 0, 0);
+        let mut player_entity_writer = self.ecs.write_resource::<Entity>();
+        *player_entity_writer = player_entity;
+    }
+
+    // Build a new map and place the player
+    self.generate_world_map(1);                                          
+}
+```
+
+And there we go - `cargo run` gives the same game we've had for a while, and we've cut out a bunch of code. Refactors that make things smaller rock!
 
 **The source code for this chapter may be found [here](https://github.com/thebracket/rustrogueliketutorial/tree/master/chapter-24-map-testing)**
 

+ 1 - 0
chapter-24-map-testing/Cargo.toml

@@ -12,6 +12,7 @@ specs = { version = "0.15.0", features = ["serde"] }
 specs-derive = "0.4.0"
 serde= { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
+generator = "0.6.18"
 
 [target.'cfg(any(target_arch = "wasm32"))'.dependencies]
 web-sys = { version = "0.3", features=["console"] }

+ 28 - 52
chapter-24-map-testing/src/main.rs

@@ -36,6 +36,8 @@ pub mod hunger_system;
 pub mod rex_assets;
 pub mod trigger_system;
 pub mod map_builders;
+#[macro_use]
+extern crate generator;
 
 rltk::add_wasm_support!();
 
@@ -306,43 +308,15 @@ impl State {
         }
 
         // Build a new map and place the player
-        let mut builder;
-        let worldmap;
         let current_depth;
-        let player_start;
         {
-            let mut worldmap_resource = self.ecs.write_resource::<Map>();
+            let worldmap_resource = self.ecs.fetch::<Map>();
             current_depth = worldmap_resource.depth;
-            builder = map_builders::random_builder(current_depth + 1);
-            builder.build_map();
-            *worldmap_resource = builder.get_map();
-            player_start = builder.get_starting_position();
-            worldmap = worldmap_resource.clone();
         }
-
-        // Spawn bad guys
-        builder.spawn_entities(&mut self.ecs);
-
-        // Place the player and update resources
-        let (player_x, player_y) = (player_start.x, player_start.y);
-        let mut player_position = self.ecs.write_resource::<Point>();
-        *player_position = Point::new(player_x, player_y);
-        let mut position_components = self.ecs.write_storage::<Position>();
-        let player_entity = self.ecs.fetch::<Entity>();
-        let player_pos_comp = position_components.get_mut(*player_entity);
-        if let Some(player_pos_comp) = player_pos_comp {
-            player_pos_comp.x = player_x;
-            player_pos_comp.y = player_y;
-        }
-
-        // Mark the player's visibility as dirty
-        let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
-        let vs = viewshed_components.get_mut(*player_entity);
-        if let Some(vs) = vs {
-            vs.dirty = true;
-        }        
+        self.generate_world_map(current_depth + 1);
 
         // Notify the player and give them some health
+        let player_entity = self.ecs.fetch::<Entity>();
         let mut gamelog = self.ecs.fetch_mut::<gamelog::GameLog>();
         gamelog.entries.insert(0, "You descend to the next level, and take a moment to heal.".to_string());
         let mut player_health_store = self.ecs.write_storage::<CombatStats>();
@@ -362,14 +336,25 @@ impl State {
             self.ecs.delete_entity(*del).expect("Deletion failed");
         }
 
+        // Spawn a new player
+        {
+            let player_entity = spawner::player(&mut self.ecs, 0, 0);
+            let mut player_entity_writer = self.ecs.write_resource::<Entity>();
+            *player_entity_writer = player_entity;
+        }
+
         // Build a new map and place the player
-        let mut builder = map_builders::random_builder(1);
+        self.generate_world_map(1);                                          
+    }
+
+    fn generate_world_map(&mut self, new_depth : i32) {
+        let mut builder = map_builders::random_builder(new_depth);
+        builder.build_map();
         let player_start;
         {
             let mut worldmap_resource = self.ecs.write_resource::<Map>();
-            builder.build_map();
-            player_start = builder.get_starting_position();
             *worldmap_resource = builder.get_map();
+            player_start = builder.get_starting_position();
         }
 
         // Spawn bad guys
@@ -377,13 +362,11 @@ impl State {
 
         // Place the player and update resources
         let (player_x, player_y) = (player_start.x, player_start.y);
-        let player_entity = spawner::player(&mut self.ecs, player_x, player_y);
         let mut player_position = self.ecs.write_resource::<Point>();
         *player_position = Point::new(player_x, player_y);
         let mut position_components = self.ecs.write_storage::<Position>();
-        let mut player_entity_writer = self.ecs.write_resource::<Entity>();
-        *player_entity_writer = player_entity;
-        let player_pos_comp = position_components.get_mut(player_entity);
+        let player_entity = self.ecs.fetch::<Entity>();
+        let player_pos_comp = position_components.get_mut(*player_entity);
         if let Some(player_pos_comp) = player_pos_comp {
             player_pos_comp.x = player_x;
             player_pos_comp.y = player_y;
@@ -391,10 +374,10 @@ impl State {
 
         // Mark the player's visibility as dirty
         let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
-        let vs = viewshed_components.get_mut(player_entity);
+        let vs = viewshed_components.get_mut(*player_entity);
         if let Some(vs) = vs {
             vs.dirty = true;
-        }                                               
+        } 
     }
 }
 
@@ -443,24 +426,17 @@ fn main() {
 
     gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
 
-    let mut builder = map_builders::random_builder(1);
-    builder.build_map();
-    let player_start = builder.get_starting_position();
-    let map = builder.get_map();
-    let (player_x, player_y) = (player_start.x, player_start.y);
-
-    let player_entity = spawner::player(&mut gs.ecs, player_x, player_y);
-
+    gs.ecs.insert(Map::new(1));
+    gs.ecs.insert(Point::new(0, 0));
     gs.ecs.insert(rltk::RandomNumberGenerator::new());
-    builder.spawn_entities(&mut gs.ecs);
-
-    gs.ecs.insert(map);
-    gs.ecs.insert(Point::new(player_x, player_y));
+    let player_entity = spawner::player(&mut gs.ecs, 0, 0);
     gs.ecs.insert(player_entity);
     gs.ecs.insert(RunState::MainMenu{ menu_selection: gui::MainMenuSelection::NewGame });
     gs.ecs.insert(gamelog::GameLog{ entries : vec!["Welcome to Rusty Roguelike".to_string()] });
     gs.ecs.insert(particle_system::ParticleBuilder::new());
     gs.ecs.insert(rex_assets::RexAssets::new());
 
+    gs.generate_world_map(1);
+
     rltk::main_loop(context, gs);
 }

+ 0 - 1
chapter-24-map-testing/src/map_builders/mod.rs

@@ -16,4 +16,3 @@ pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
     // Note that until we have a second map type, this isn't even slighlty random
     Box::new(SimpleMapBuilder::new(new_depth))
 }
-

+ 1 - 0
chapter-24-map-testing/src/map_builders/simple_map.rs

@@ -3,6 +3,7 @@ use super::{MapBuilder, Map, Rect, apply_room_to_map,
     Position, spawner};
 use rltk::RandomNumberGenerator;
 use specs::prelude::*;
+use generator::{Generator, Gn};
 
 pub struct SimpleMapBuilder {
     map : Map,