RCRPG/Rust: Difference between revisions

From Rosetta Code
Content added Content deleted
(Created page with "{{collection|RCRPG}} Rust version of RCRPG. ==Code== <lang rust> use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use std::{io,...")
 
(Refactoring)
Line 4: Line 4:
==Code==
==Code==
<lang rust>
<lang rust>
//! Implementation of the simple text-based game [RCRPG](https://web.archive.org/web/20080212201605/http://shortcircuit.us/muddy-kinda-like-a-mud-but-single-player/)
//! in Rust

use rand::prelude::*;
use std::borrow::BorrowMut;
use std::collections::{HashMap, HashSet};
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Display};
use std::iter::FromIterator;
use std::iter::FromIterator;
use std::{io, fmt};
use std::fmt::{Display, Debug};
use std::borrow::BorrowMut;
use std::ops::Add;
use std::ops::Add;
use rand::prelude::*;
use std::{fmt, io};

///////////////
// CONSTANTS //
///////////////


/// Maps each Locations to a direction
const DIRECTION_MAPPING: [(Location, Direction); 6] = [
const DIRECTION_MAPPING: [(Location, Direction); 6] = [
(Location(0, -1, 0), Direction::North),
(Location(0, -1, 0), Direction::North),
Line 25: Line 25:
];
];


/// Objects possessed by the player
///////////
type Inventory = HashSet<Object>;
// TYPES //
/// Maps the (possibly user-defined) aliases to their actual action, so that for instance a player
///////////
/// can input either `n` or `north` to go North, and can also define new aliases

type Invetory = HashSet<Object>;
type CommandAliases = Vec<(HashSet<String>, Command)>;
type CommandAliases = Vec<(HashSet<String>, Command)>;


/// 3D coordinates of objects in the dungeon
//////////////
// LOCATION //
//////////////

#[derive(Hash, Eq, PartialEq, Copy, Clone)]
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
struct Location(i32, i32, i32);
struct Location(i32, i32, i32);
Line 53: Line 49:
}
}


/// Objects that can be found in the dungon rooms
////////////
// OBJECT //
////////////

#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
enum Object {
enum Object {
Line 75: Line 68:


impl Object {
impl Object {
/// Tries to parse a string to an object, like `"gold"` to `Object::Gold`
fn from_string(s: &str) -> Option<Object> {
fn from_string(s: &str) -> Option<Object> {
match s {
match s {
Line 80: Line 74:
"sledge" => Some(Object::Sledge),
"sledge" => Some(Object::Sledge),
"gold" => Some(Object::Gold),
"gold" => Some(Object::Gold),
_ => None
_ => None,
}
}
}
}
}
}


/// Player information
////////////
// PLAYER //
////////////

struct Player {
struct Player {
/// Room where the player currently is
location: Location,
location: Location,
/// The objects carried by the player
inventory: Invetory,
inventory: Inventory,
/// The object wieled by the player, if any
equipped: Option<Object>,
equipped: Option<Object>,
}
}


/// Information about each room of the dungeon
//////////
// ROOM //
//////////

struct Room {
struct Room {
/// Fixed description for special rooms (like the first one or the prize room)
description: Option<String>,
description: Option<String>,
/// Objects currently in the room
objects: Invetory,
objects: Inventory,
}
}


Line 112: Line 105:
}
}


/// Sets the room description
fn with_description(mut self, descrition: &str) -> Self {
self.description = Some(descrition.to_string());
fn with_description(mut self, description: &str) -> Self {
self.description = Some(description.to_string());
self
self
}
}


/// Sets the objects in the room
fn with_objects(mut self, objects: Vec<Object>) -> Self {
fn with_objects(mut self, objects: Vec<Object>) -> Self {
self.objects.extend(objects);
self.objects.extend(objects);
Line 122: Line 117:
}
}


/// Adds some randoms objects to the room
fn with_random_objects(mut self, rng: &mut ThreadRng ) -> Self {
fn with_random_objects(mut self, rng: &mut ThreadRng) -> Self {
let objects: Vec<_> = vec![
let objects: Vec<_> = vec![
if rng.gen::<f32>() < 0.33 { Some(Object::Sledge) } else { None },
if rng.gen::<f32>() < 0.33 {
if rng.gen::<f32>() < 0.33 { Some(Object::Ladder) } else { None },
Some(Object::Sledge)
if rng.gen::<f32>() < 0.33 { Some(Object::Gold) } else { None },
} else {
None
].iter().filter_map(|o| *o).collect();
},
if rng.gen::<f32>() < 0.33 {
Some(Object::Ladder)
} else {
None
},
if rng.gen::<f32>() < 0.33 {
Some(Object::Gold)
} else {
None
},
]
.iter()
.filter_map(|o| *o)
.collect();


self.objects.extend(objects);
self.objects.extend(objects);
Line 134: Line 145:
}
}


/// Cardinat directions
/////////////
// DUNGEON //
/////////////

#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq)]
enum Direction {
enum Direction {
Line 162: Line 170:


impl Direction {
impl Direction {
/// Tries to parse a string to a direction, like `"north"` to `Direction::North`
fn from_string(s: &str) -> Option<Direction> {
fn from_string(s: &str) -> Option<Direction> {
match s {
match s {
Line 170: Line 179:
"down" => Some(Direction::Down),
"down" => Some(Direction::Down),
"up" => Some(Direction::Up),
"up" => Some(Direction::Up),
_ => None
_ => None,
}
}
}
}


/// Returns the normalized 3D point of the location, for instance `Direction::North` is
fn to_location(&self) -> Location {
/// `(0, -1, 0)` where `(x, y, z)`
DIRECTION_MAPPING.iter()
fn to_location(self) -> Location {
.find(|d| d.1 == *self)
.unwrap()
DIRECTION_MAPPING.iter().find(|d| d.1 == self).unwrap().0
.0
}
}
}
}


/// Collection of rooms
struct Dungeon {
struct Dungeon {
rooms: HashMap<Location, Room>
/// The rooms that make up the dungeon
rooms: HashMap<Location, Room>,
}
}


Line 190: Line 200:
Dungeon {
Dungeon {
rooms: HashMap::from_iter(vec![
rooms: HashMap::from_iter(vec![
(Location(0, 0, 0), Room::new()
(
.with_description("The room where it all started...")
Location(0, 0, 0),
.with_objects(vec![Object::Ladder, Object::Sledge])),
Room::new()
(Location(1, 1, 5), Room::new()
.with_description("The room where it all started...")
.with_description("You found it! Lots of gold!"))
.with_objects(vec![Object::Ladder, Object::Sledge]),
])
),
(
Location(1, 1, 5),
Room::new().with_description("You found it! Lots of gold!"),
),
]),
}
}
}
}


/// Given a room location, returns the list of `Direction`s that lead to other rooms
fn exits_for_room(&self, location: Location) -> Vec<Direction> {
fn exits_for_room(&self, location: Location) -> Vec<Direction> {
DIRECTION_MAPPING.iter().filter_map(|d| {
DIRECTION_MAPPING
let location_to_test = location + d.0;
.iter()
.filter_map(|d| {
let location_to_test = location + d.0;


if self.rooms.contains_key(&location_to_test) {
if self.rooms.contains_key(&location_to_test) {
return Some(d.1);
return Some(d.1);
}
}
None
None
}).collect()
})
.collect()
}
}
}
}


/// Collection of all the available commands to interact to the dungeon world
//////////////
// COMMANDS //
//////////////

#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone)]
enum Command {
enum Command {
Line 234: Line 250:
}
}


/// Returns the list of all the default command aliases
fn default_aliases() -> CommandAliases {
fn default_aliases() -> CommandAliases {
vec![
vec![
(
(vec!["n".to_string(), "north".to_string()].into_iter().collect(), Command::North),
(vec!["s".to_string(), "south".to_string()].into_iter().collect(), Command::South),
vec!["n".to_string(), "north".to_string()]
(vec!["w".to_string(), "west".to_string()].into_iter().collect(), Command::West),
.into_iter()
(vec!["e".to_string(), "east".to_string()].into_iter().collect(), Command::East),
.collect(),
(vec!["d".to_string(), "down".to_string()].into_iter().collect(), Command::Down),
Command::North,
),
(vec!["u".to_string(), "up".to_string()].into_iter().collect(), Command::Up),
(
(vec!["help".to_string()].into_iter().collect(), Command::Help),
vec!["s".to_string(), "south".to_string()]
.into_iter()
.collect(),
Command::South,
),
(
vec!["w".to_string(), "west".to_string()]
.into_iter()
.collect(),
Command::West,
),
(
vec!["e".to_string(), "east".to_string()]
.into_iter()
.collect(),
Command::East,
),
(
vec!["d".to_string(), "down".to_string()]
.into_iter()
.collect(),
Command::Down,
),
(
vec!["u".to_string(), "up".to_string()]
.into_iter()
.collect(),
Command::Up,
),
(
vec!["help".to_string()].into_iter().collect(),
Command::Help,
),
(vec!["dig".to_string()].into_iter().collect(), Command::Dig),
(vec!["dig".to_string()].into_iter().collect(), Command::Dig),
(
(vec!["l".to_string(), "look".to_string()].into_iter().collect(), Command::Look),
(vec!["i".to_string(), "inventory".to_string()].into_iter().collect(), Command::Inventory),
vec!["l".to_string(), "look".to_string()]
(vec!["take".to_string()].into_iter().collect(), Command::Take),
.into_iter()
(vec!["drop".to_string()].into_iter().collect(), Command::Drop),
.collect(),
(vec!["equip".to_string()].into_iter().collect(), Command::Equip),
Command::Look,
),
(vec!["unequip".to_string()].into_iter().collect(), Command::Unequip),
(
(vec!["alias".to_string()].into_iter().collect(), Command::Alias),
vec!["i".to_string(), "inventory".to_string()]
.into_iter()
.collect(),
Command::Inventory,
),
(
vec!["take".to_string()].into_iter().collect(),
Command::Take,
),
(
vec!["drop".to_string()].into_iter().collect(),
Command::Drop,
),
(
vec!["equip".to_string()].into_iter().collect(),
Command::Equip,
),
(
vec!["unequip".to_string()].into_iter().collect(),
Command::Unequip,
),
(
vec!["alias".to_string()].into_iter().collect(),
Command::Alias,
),
]
]
}
}


/// Tries to parse a string to a command also taking into account the aliases
fn find_command(command: &str, aliases: &CommandAliases) -> Option<Command> {
fn find_command(command: &str, aliases: &[(HashSet<String>, Command)]) -> Option<Command> {
let command = command.to_lowercase();
let command = command.to_lowercase();


aliases.iter()
aliases.iter().find(|a| a.0.contains(&command)).map(|a| a.1)
.find(|a| a.0.contains(&command))
.map(|a| a.1)
}
}


/// Prints the help string
fn help() {
fn help() {
println!(
println!("You need a sledge to dig rooms and ladders to go upwards.
"You need a sledge to dig rooms and ladders to go upwards.
Valid commands are: directions (north, south...), dig, take, drop, equip, inventory and look.
Valid commands are: directions (north, south...), dig, take, drop, equip, inventory and look.
Additionally you can tag rooms with the 'name' command and alias commands with 'alias'.
Additionally you can tag rooms with the 'name' command and alias commands with 'alias'.
Have fun!")
Have fun!"
)
}
}


/// Defines a new alias for a command
fn alias(command_aliases: &mut CommandAliases, args: &[&str]) {
fn alias(command_aliases: &mut CommandAliases, args: &[&str]) {
if args.len() < 2 {
if args.len() < 2 {
Line 292: Line 370:
}
}


/// Describes the current rooom
fn look(player: &Player, dungeon: &Dungeon) {
fn look(player: &Player, dungeon: &Dungeon) {
let room = &dungeon.rooms[&player.location];
let room = &dungeon.rooms[&player.location];
Line 300: Line 379:
print!("Room at {:?}.", player.location);
print!("Room at {:?}.", player.location);
}
}



if !room.objects.is_empty() {
if !room.objects.is_empty() {
print!(" On the floor you can see: {}.", room.objects
print!(
.iter()
" On the floor you can see: {}.",
.map(|o| o.to_string())
room.objects
.collect::<Vec<String>>()
.iter()
.join(", "));
.map(|o| o.to_string())
.collect::<Vec<String>>()
.join(", ")
);
}
}


Line 314: Line 395:
0 => println!(" There are no exits in this room."),
0 => println!(" There are no exits in this room."),
1 => println!(" There is one exit: {}.", room_exits[0].to_string()),
1 => println!(" There is one exit: {}.", room_exits[0].to_string()),
_ => println!(" Exits: {}.", room_exits.iter()
_ => println!(
.map(|o| o.to_string())
" Exits: {}.",
.collect::<Vec<String>>()
room_exits
.join(", "))
.iter()
.map(|o| o.to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
}


/// Grabs an object lying on the floor of a room and puts it into the player's inventory

fn take(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {
fn take(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {
if args.is_empty() {
if args.is_empty() {
Line 328: Line 413:
println!("There is nothing to take here")
println!("There is nothing to take here")
} else if args[0] == "all" {
} else if args[0] == "all" {
let room_objects = dungeon.rooms.get_mut(&player.location)
let room_objects = dungeon
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.expect("The player is in a room that should not exist!")
.objects
.objects
Line 338: Line 425:
println!("All items taken");
println!("All items taken");
} else if let Some(object) = Object::from_string(args[0]) {
} else if let Some(object) = Object::from_string(args[0]) {
let room_objects = dungeon.rooms.get_mut(&player.location)
let room_objects = dungeon
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.expect("The player is in a room that should not exist!")
.objects
.objects
Line 353: Line 442:
}
}


/// Removes an object from the player's inventory and leaves it lying on the current room's floor
fn drop(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {
fn drop(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {
if args.is_empty() {
if args.is_empty() {
Line 359: Line 449:
println!("You are not carrying anything")
println!("You are not carrying anything")
} else if args[0] == "all" {
} else if args[0] == "all" {
let room_objects = dungeon.rooms.get_mut(&player.location)
let room_objects = dungeon
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.expect("The player is in a room that should not exist!")
.objects
.objects
Line 369: Line 461:
println!("All items dropped");
println!("All items dropped");
} else if let Some(object) = Object::from_string(args[0]) {
} else if let Some(object) = Object::from_string(args[0]) {
let room_objects = dungeon.rooms.get_mut(&player.location)
let room_objects = dungeon
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.expect("The player is in a room that should not exist!")
.objects
.objects
Line 384: Line 478:
}
}


/// Prints the list of object currently carries by the player
fn inventory(player: &Player) {
fn inventory(player: &Player) {
if player.inventory.is_empty() {
if player.inventory.is_empty() {
println!("You are not carrying anything")
println!("You are not carrying anything")
} else {
} else {
println!("You are carrying: {}", player.inventory
println!(
.iter()
"You are carrying: {}",
.map(|o| o.to_string())
player
.collect::<Vec<String>>()
.inventory
.join(", "));
.iter()
.map(|o| o.to_string())
.collect::<Vec<String>>()
.join(", ")
);
}
}
}
}


/// Digs a tunnel to a new room connected to the current one
#[allow(clippy::map_entry)]
#[allow(clippy::map_entry)]
fn dig(player: &Player, dungeon: &mut Dungeon, rng: &mut ThreadRng, args: &[&str]) {
fn dig(player: &Player, dungeon: &mut Dungeon, rng: &mut ThreadRng, args: &[&str]) {
Line 425: Line 525:
}
}


/// Moves the player to an adjacent room
fn goto(player: &mut Player, dungeon: &Dungeon, direction: &Direction) {
fn goto(player: &mut Player, dungeon: &Dungeon, direction: Direction) {
if direction == &Direction::North && !dungeon.rooms[&player.location].objects.contains(&Object::Ladder) {
if direction == Direction::North
&& !dungeon.rooms[&player.location]
.objects
.contains(&Object::Ladder)
{
println!("You can't go upwards without a ladder!");
println!("You can't go upwards without a ladder!");
} else {
} else {
Line 439: Line 544:
}
}


/// Equips an object
fn equip(player: &mut Player, args: &[&str]) {
fn equip(player: &mut Player, args: &[&str]) {
if args.is_empty() {
if args.is_empty() {
Line 454: Line 560:
}
}


/// Unequips an object
fn unequip(player: &mut Player) {
fn unequip(player: &mut Player) {
if player.equipped.is_some() {
if player.equipped.is_some() {
Line 463: Line 570:
}
}


/// Main game loop
//////////
// MAIN //
//////////

fn main() {
fn main() {
let mut command_aliases = default_aliases();
let mut command_aliases = default_aliases();
Line 483: Line 587:
loop {
loop {
let mut input = String::new();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Cannot read from stdin");
io::stdin()
.read_line(&mut input)
.expect("Cannot read from stdin");
let input: &str = &input.trim().to_lowercase();
let input: &str = &input.trim().to_lowercase();


Line 499: Line 605:
Some(Command::Equip) => equip(&mut player, &splitted[1..]),
Some(Command::Equip) => equip(&mut player, &splitted[1..]),
Some(Command::Unequip) => unequip(&mut player),
Some(Command::Unequip) => unequip(&mut player),
Some(Command::North) => goto(&mut player, &dungeon, &Direction::North),
Some(Command::North) => goto(&mut player, &dungeon, Direction::North),
Some(Command::South) => goto(&mut player, &dungeon, &Direction::South),
Some(Command::South) => goto(&mut player, &dungeon, Direction::South),
Some(Command::West) => goto(&mut player, &dungeon, &Direction::West),
Some(Command::West) => goto(&mut player, &dungeon, Direction::West),
Some(Command::East) => goto(&mut player, &dungeon, &Direction::East),
Some(Command::East) => goto(&mut player, &dungeon, Direction::East),
Some(Command::Down) => goto(&mut player, &dungeon, &Direction::Down),
Some(Command::Down) => goto(&mut player, &dungeon, Direction::Down),
Some(Command::Up) => goto(&mut player, &dungeon, &Direction::Up),
Some(Command::Up) => goto(&mut player, &dungeon, Direction::Up),
_ => println!("I don't know what you mean.")
_ => println!("I don't know what you mean."),
}
}
}
}
}
}
}</lang>
}

</lang>

Revision as of 12:54, 6 September 2019

RCRPG/Rust is part of RCRPG. You may find other members of RCRPG at Category:RCRPG.

Rust version of RCRPG.

Code

<lang rust> //! Implementation of the simple text-based game [RCRPG](https://web.archive.org/web/20080212201605/http://shortcircuit.us/muddy-kinda-like-a-mud-but-single-player/) //! in Rust

use rand::prelude::*; use std::borrow::BorrowMut; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Display}; use std::iter::FromIterator; use std::ops::Add; use std::{fmt, io};

/// Maps each Locations to a direction const DIRECTION_MAPPING: [(Location, Direction); 6] = [

   (Location(0, -1, 0), Direction::North),
   (Location(0, 1, 0), Direction::South),
   (Location(-1, 0, 0), Direction::West),
   (Location(1, 0, 0), Direction::East),
   (Location(0, 0, 1), Direction::Down),
   (Location(0, 0, -1), Direction::Up),

];

/// Objects possessed by the player type Inventory = HashSet<Object>; /// Maps the (possibly user-defined) aliases to their actual action, so that for instance a player /// can input either `n` or `north` to go North, and can also define new aliases type CommandAliases = Vec<(HashSet<String>, Command)>;

/// 3D coordinates of objects in the dungeon

  1. [derive(Hash, Eq, PartialEq, Copy, Clone)]

struct Location(i32, i32, i32);

impl Add for Location {

   type Output = Self;
   fn add(self, rhs: Self) -> Self::Output {
       Location(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
   }

}

impl Debug for Location {

   fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
       write!(f, "({}, {}, {})", self.0, self.1, self.2)
   }

}

/// Objects that can be found in the dungon rooms

  1. [derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]

enum Object {

   Ladder,
   Sledge,
   Gold,

}

impl Display for Object {

   fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
       match *self {
           Object::Ladder => write!(f, "a ladder"),
           Object::Sledge => write!(f, "a sledge"),
           Object::Gold => write!(f, "some gold"),
       }
   }

}

impl Object {

   /// Tries to parse a string to an object, like `"gold"` to `Object::Gold`
   fn from_string(s: &str) -> Option<Object> {
       match s {
           "ladder" => Some(Object::Ladder),
           "sledge" => Some(Object::Sledge),
           "gold" => Some(Object::Gold),
           _ => None,
       }
   }

}

/// Player information struct Player {

   /// Room where the player currently is
   location: Location,
   /// The objects carried by the player
   inventory: Inventory,
   /// The object wieled by the player, if any
   equipped: Option<Object>,

}

/// Information about each room of the dungeon struct Room {

   /// Fixed description for special rooms (like the first one or the prize room)
   description: Option<String>,
   /// Objects currently in the room
   objects: Inventory,

}

impl Room {

   fn new() -> Self {
       Room {
           description: None,
           objects: HashSet::new(),
       }
   }
   /// Sets the room description
   fn with_description(mut self, description: &str) -> Self {
       self.description = Some(description.to_string());
       self
   }
   /// Sets the objects in the room
   fn with_objects(mut self, objects: Vec<Object>) -> Self {
       self.objects.extend(objects);
       self
   }
   /// Adds some randoms objects to the room
   fn with_random_objects(mut self, rng: &mut ThreadRng) -> Self {
       let objects: Vec<_> = vec![
           if rng.gen::<f32>() < 0.33 {
               Some(Object::Sledge)
           } else {
               None
           },
           if rng.gen::<f32>() < 0.33 {
               Some(Object::Ladder)
           } else {
               None
           },
           if rng.gen::<f32>() < 0.33 {
               Some(Object::Gold)
           } else {
               None
           },
       ]
       .iter()
       .filter_map(|o| *o)
       .collect();
       self.objects.extend(objects);
       self
   }

}

/// Cardinat directions

  1. [derive(Copy, Clone, Eq, PartialEq)]

enum Direction {

   North,
   South,
   West,
   East,
   Down,
   Up,

}

impl Display for Direction {

   fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
       match *self {
           Direction::North => write!(f, "north"),
           Direction::South => write!(f, "south"),
           Direction::West => write!(f, "west"),
           Direction::East => write!(f, "east"),
           Direction::Down => write!(f, "down"),
           Direction::Up => write!(f, "up"),
       }
   }

}

impl Direction {

   /// Tries to parse a string to a direction, like `"north"` to `Direction::North`
   fn from_string(s: &str) -> Option<Direction> {
       match s {
           "north" => Some(Direction::North),
           "south" => Some(Direction::South),
           "west" => Some(Direction::West),
           "east" => Some(Direction::East),
           "down" => Some(Direction::Down),
           "up" => Some(Direction::Up),
           _ => None,
       }
   }
   /// Returns the normalized 3D point of the location, for instance `Direction::North` is
   /// `(0, -1, 0)` where `(x, y, z)`
   fn to_location(self) -> Location {
       DIRECTION_MAPPING.iter().find(|d| d.1 == self).unwrap().0
   }

}

/// Collection of rooms struct Dungeon {

   /// The rooms that make up the dungeon
   rooms: HashMap<Location, Room>,

}

impl Dungeon {

   fn new() -> Self {
       Dungeon {
           rooms: HashMap::from_iter(vec![
               (
                   Location(0, 0, 0),
                   Room::new()
                       .with_description("The room where it all started...")
                       .with_objects(vec![Object::Ladder, Object::Sledge]),
               ),
               (
                   Location(1, 1, 5),
                   Room::new().with_description("You found it! Lots of gold!"),
               ),
           ]),
       }
   }
   /// Given a room location, returns the list of `Direction`s that lead to other rooms
   fn exits_for_room(&self, location: Location) -> Vec<Direction> {
       DIRECTION_MAPPING
           .iter()
           .filter_map(|d| {
               let location_to_test = location + d.0;
               if self.rooms.contains_key(&location_to_test) {
                   return Some(d.1);
               }
               None
           })
           .collect()
   }

}

/// Collection of all the available commands to interact to the dungeon world

  1. [derive(Debug, Copy, Clone)]

enum Command {

   North,
   South,
   West,
   East,
   Down,
   Up,
   Help,
   Dig,
   Look,
   Inventory,
   Take,
   Drop,
   Equip,
   Unequip,
   Alias,

}

/// Returns the list of all the default command aliases fn default_aliases() -> CommandAliases {

   vec![
       (
           vec!["n".to_string(), "north".to_string()]
               .into_iter()
               .collect(),
           Command::North,
       ),
       (
           vec!["s".to_string(), "south".to_string()]
               .into_iter()
               .collect(),
           Command::South,
       ),
       (
           vec!["w".to_string(), "west".to_string()]
               .into_iter()
               .collect(),
           Command::West,
       ),
       (
           vec!["e".to_string(), "east".to_string()]
               .into_iter()
               .collect(),
           Command::East,
       ),
       (
           vec!["d".to_string(), "down".to_string()]
               .into_iter()
               .collect(),
           Command::Down,
       ),
       (
           vec!["u".to_string(), "up".to_string()]
               .into_iter()
               .collect(),
           Command::Up,
       ),
       (
           vec!["help".to_string()].into_iter().collect(),
           Command::Help,
       ),
       (vec!["dig".to_string()].into_iter().collect(), Command::Dig),
       (
           vec!["l".to_string(), "look".to_string()]
               .into_iter()
               .collect(),
           Command::Look,
       ),
       (
           vec!["i".to_string(), "inventory".to_string()]
               .into_iter()
               .collect(),
           Command::Inventory,
       ),
       (
           vec!["take".to_string()].into_iter().collect(),
           Command::Take,
       ),
       (
           vec!["drop".to_string()].into_iter().collect(),
           Command::Drop,
       ),
       (
           vec!["equip".to_string()].into_iter().collect(),
           Command::Equip,
       ),
       (
           vec!["unequip".to_string()].into_iter().collect(),
           Command::Unequip,
       ),
       (
           vec!["alias".to_string()].into_iter().collect(),
           Command::Alias,
       ),
   ]

}

/// Tries to parse a string to a command also taking into account the aliases fn find_command(command: &str, aliases: &[(HashSet<String>, Command)]) -> Option<Command> {

   let command = command.to_lowercase();
   aliases.iter().find(|a| a.0.contains(&command)).map(|a| a.1)

}

/// Prints the help string fn help() {

   println!(
       "You need a sledge to dig rooms and ladders to go upwards.

Valid commands are: directions (north, south...), dig, take, drop, equip, inventory and look. Additionally you can tag rooms with the 'name' command and alias commands with 'alias'. Have fun!"

   )

}

/// Defines a new alias for a command fn alias(command_aliases: &mut CommandAliases, args: &[&str]) {

   if args.len() < 2 {
       println!("To assign an alias: alias CMQ NEW_ALIAS");
   } else {
       let command = args[0].to_lowercase();
       let new_alias = args[1].to_lowercase();
       let mut found = false;
       for ca in command_aliases {
           if ca.0.contains(&command) {
               ca.0.insert(new_alias.clone());
               found = true;
           }
       }
       if found {
           println!("You can use \"{}\" in lieu of \"{}\"", new_alias, command);
       } else {
           println!("The commands \"{}\" does not exist", command);
       }
   }

}

/// Describes the current rooom fn look(player: &Player, dungeon: &Dungeon) {

   let room = &dungeon.rooms[&player.location];
   if let Some(description) = &room.description {
       print!("{}", description);
   } else {
       print!("Room at {:?}.", player.location);
   }
   if !room.objects.is_empty() {
       print!(
           " On the floor you can see: {}.",
           room.objects
               .iter()
               .map(|o| o.to_string())
               .collect::<Vec<String>>()
               .join(", ")
       );
   }
   let room_exits = dungeon.exits_for_room(player.location);
   match room_exits.len() {
       0 => println!(" There are no exits in this room."),
       1 => println!(" There is one exit: {}.", room_exits[0].to_string()),
       _ => println!(
           " Exits: {}.",
           room_exits
               .iter()
               .map(|o| o.to_string())
               .collect::<Vec<String>>()
               .join(", ")
       ),
   }

}

/// Grabs an object lying on the floor of a room and puts it into the player's inventory fn take(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {

   if args.is_empty() {
       println!("To take something: take OBJECT|all")
   } else if dungeon.rooms[&player.location].objects.is_empty() {
       println!("There is nothing to take here")
   } else if args[0] == "all" {
       let room_objects = dungeon
           .rooms
           .get_mut(&player.location)
           .expect("The player is in a room that should not exist!")
           .objects
           .borrow_mut();
       player.inventory.extend(room_objects.iter());
       room_objects.clear();
       println!("All items taken");
   } else if let Some(object) = Object::from_string(args[0]) {
       let room_objects = dungeon
           .rooms
           .get_mut(&player.location)
           .expect("The player is in a room that should not exist!")
           .objects
           .borrow_mut();
       if room_objects.contains(&object) {
           player.inventory.insert(object);
           room_objects.remove(&object);
           println!("Taken");
       }
   } else {
       println!("You can't see anything like that here")
   }

}

/// Removes an object from the player's inventory and leaves it lying on the current room's floor fn drop(player: &mut Player, dungeon: &mut Dungeon, args: &[&str]) {

   if args.is_empty() {
       println!("To drop something: drop OBJECT|all")
   } else if player.inventory.is_empty() {
       println!("You are not carrying anything")
   } else if args[0] == "all" {
       let room_objects = dungeon
           .rooms
           .get_mut(&player.location)
           .expect("The player is in a room that should not exist!")
           .objects
           .borrow_mut();
       room_objects.extend(player.inventory.iter());
       player.inventory.clear();
       println!("All items dropped");
   } else if let Some(object) = Object::from_string(args[0]) {
       let room_objects = dungeon
           .rooms
           .get_mut(&player.location)
           .expect("The player is in a room that should not exist!")
           .objects
           .borrow_mut();
       if player.inventory.contains(&object) {
           player.inventory.remove(&object);
           room_objects.insert(object);
           println!("Dropped");
       }
   } else {
       println!("You don't have anything like that")
   }

}

/// Prints the list of object currently carries by the player fn inventory(player: &Player) {

   if player.inventory.is_empty() {
       println!("You are not carrying anything")
   } else {
       println!(
           "You are carrying: {}",
           player
               .inventory
               .iter()
               .map(|o| o.to_string())
               .collect::<Vec<String>>()
               .join(", ")
       );
   }

}

/// Digs a tunnel to a new room connected to the current one

  1. [allow(clippy::map_entry)]

fn dig(player: &Player, dungeon: &mut Dungeon, rng: &mut ThreadRng, args: &[&str]) {

   if args.is_empty() {
       println!("To dig a tunnel: dig DIRECTION");
   } else if let Some(direction) = Direction::from_string(args[0]) {
       if let Some(equipped) = player.equipped {
           if equipped == Object::Sledge {
               let target_location = player.location + direction.to_location();
               if dungeon.rooms.contains_key(&target_location) {
                   println!("There is already an exit, there!");
               }
               dungeon.rooms.entry(target_location).or_insert_with(|| {
                   println!("There is now an exit {}ward", direction);
                   Room::new().with_random_objects(rng)
               });
           } else {
               println!("You cannot dig with {}", equipped);
           }
       } else {
           println!("With your bare hands?");
       }
   } else {
       println!("That is not a direction I recognize");
   }

}

/// Moves the player to an adjacent room fn goto(player: &mut Player, dungeon: &Dungeon, direction: Direction) {

   if direction == Direction::North
       && !dungeon.rooms[&player.location]
           .objects
           .contains(&Object::Ladder)
   {
       println!("You can't go upwards without a ladder!");
   } else {
       let target_location = player.location + direction.to_location();
       if !dungeon.rooms.contains_key(&target_location) {
           println!("There's no exit in that direction!");
       } else {
           player.location = target_location;
           look(player, dungeon);
       }
   }

}

/// Equips an object fn equip(player: &mut Player, args: &[&str]) {

   if args.is_empty() {
       println!("To equip something: equip OBJECT");
   } else if let Some(object) = Object::from_string(args[0]) {
       if player.inventory.contains(&object) {
           player.equipped = Some(object);
           println!("Item equipped");
       } else {
           println!("You don't have such object");
       }
   } else {
       println!("You don't have such object");
   }

}

/// Unequips an object fn unequip(player: &mut Player) {

   if player.equipped.is_some() {
       player.equipped = None;
       println!("Unequipped");
   } else {
       println!("You are already not using anything");
   }

}

/// Main game loop fn main() {

   let mut command_aliases = default_aliases();
   let mut dungeon = Dungeon::new();
   let mut player = Player {
       location: Location(0, 0, 0),
       inventory: HashSet::from_iter(vec![Object::Sledge]),
       equipped: None,
   };
   let mut rng = rand::thread_rng();
   // init
   println!("Grab the sledge and make your way to room 1,1,5 for a non-existant prize!\n");
   help();
   loop {
       let mut input = String::new();
       io::stdin()
           .read_line(&mut input)
           .expect("Cannot read from stdin");
       let input: &str = &input.trim().to_lowercase();
       let splitted = input.split_whitespace().collect::<Vec<&str>>();
       if !splitted.is_empty() {
           match find_command(splitted[0], &command_aliases) {
               Some(Command::Help) => help(),
               Some(Command::Alias) => alias(&mut command_aliases, &splitted[1..]),
               Some(Command::Look) => look(&player, &dungeon),
               Some(Command::Take) => take(&mut player, &mut dungeon, &splitted[1..]),
               Some(Command::Drop) => drop(&mut player, &mut dungeon, &splitted[1..]),
               Some(Command::Inventory) => inventory(&player),
               Some(Command::Dig) => dig(&player, &mut dungeon, &mut rng, &splitted[1..]),
               Some(Command::Equip) => equip(&mut player, &splitted[1..]),
               Some(Command::Unequip) => unequip(&mut player),
               Some(Command::North) => goto(&mut player, &dungeon, Direction::North),
               Some(Command::South) => goto(&mut player, &dungeon, Direction::South),
               Some(Command::West) => goto(&mut player, &dungeon, Direction::West),
               Some(Command::East) => goto(&mut player, &dungeon, Direction::East),
               Some(Command::Down) => goto(&mut player, &dungeon, Direction::Down),
               Some(Command::Up) => goto(&mut player, &dungeon, Direction::Up),
               _ => println!("I don't know what you mean."),
           }
       }
   }

}</lang>