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:
==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::{io, fmt};
use std::fmt::{Display, Debug};
use std::borrow::BorrowMut;
use std::ops::Add;
use randstd::prelude::*{fmt, io};
 
///////////////
// CONSTANTS //
///////////////
 
/// Maps each Locations to a direction
const DIRECTION_MAPPING: [(Location, Direction); 6] = [
(Location(0, -1, 0), Direction::North),
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)>;
 
/// 3D coordinates of objects in the dungeon
//////////////
// LOCATION //
//////////////
 
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
struct Location(i32, i32, i32);
Line 53 ⟶ 49:
}
 
/// Objects that can be found in the dungon rooms
////////////
// OBJECT //
////////////
 
#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]
enum Object {
Line 75 ⟶ 68:
 
impl Object {
/// Tries to parse a string to an object, like `"gold"` to `Object::Gold`
fn from_string(s: &str) -> Option<Object> {
match s {
Line 80 ⟶ 74:
"sledge" => Some(Object::Sledge),
"gold" => Some(Object::Gold),
_ => None,
}
}
}
 
/// Player information
////////////
// PLAYER //
////////////
 
struct Player {
/// Room where the player currently is
location: Location,
/// The objects carried by the player
inventory: Invetory,
inventory: Inventory,
/// The object wieled by the player, if any
equipped: Option<Object>,
}
 
/// Information about each room of the dungeon
//////////
// ROOM //
//////////
 
struct Room {
/// Fixed description for special rooms (like the first one or the prize room)
description: Option<String>,
/// Objects currently in the room
objects: Invetory,
objects: Inventory,
}
 
Line 112 ⟶ 105:
}
 
/// Sets the room description
fn with_description(mut self, descrition: &str) -> Self {
fn with_description(mut self, self.description: =&str) Some(descrition.to_string());-> 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);
Line 122 ⟶ 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![
if rng.gen::<f32>() < 0.33 { Some(Object::Sledge) } else { None },
if rng.gen::<f32>() < 0.33 { Some(Object::LadderSledge) } else { None },
if rng.gen::<f32>() < 0.33 { Some(Object::Gold) } else { None },
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);
Line 134 ⟶ 145:
}
 
/// Cardinat directions
/////////////
// DUNGEON //
/////////////
 
#[derive(Copy, Clone, Eq, PartialEq)]
enum Direction {
Line 162 ⟶ 170:
 
impl Direction {
/// Tries to parse a string to a direction, like `"north"` to `Direction::North`
fn from_string(s: &str) -> Option<Direction> {
match s {
Line 170 ⟶ 179:
"down" => Some(Direction::Down),
"up" => Some(Direction::Up),
_ => 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)
DIRECTION_MAPPING.iter().find(|d| d.1 == self).unwrap().0
.0
}
}
 
/// Collection of rooms
struct Dungeon {
/// The rooms: HashMap<Location,that make up the Room>dungeon
rooms: HashMap<Location, Room>,
}
 
Line 190 ⟶ 200:
Dungeon {
rooms: HashMap::from_iter(vec![
(Location(0, 0, 0), Room::new()
.with_descriptionLocation("The0, room0, where it all started..."0),
.with_objects(vec![ObjectRoom::Ladder, Object::Sledge])new(),
(Location(1, 1, 5), Room::new .with_description("The room where it all started...")
.with_description("You found it! Lots of.with_objects(vec![Object::Ladder, gold!")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;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()
.collect()
}
}
 
/// Collection of all the available commands to interact to the dungeon world
//////////////
// COMMANDS //
//////////////
 
#[derive(Debug, Copy, Clone)]
enum Command {
Line 234 ⟶ 250:
}
 
/// 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!["sn".to_string(), "southnorth".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)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!["l".to_string(), "look".to_string()].into_iter().collect(), Command::Look),
( vec!["il".to_string(), "inventorylook".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)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();
 
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() {
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.
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 {
Line 292 ⟶ 370:
}
 
/// Describes the current rooom
fn look(player: &Player, dungeon: &Dungeon) {
let room = &dungeon.rooms[&player.location];
Line 300 ⟶ 379:
print!("Room at {:?}.", player.location);
}
 
 
if !room.objects.is_empty() {
print!(" On the floor you can see: {}.", room.objects
" On the floor you can see: {}.iter()",
room.map(|o| o.to_string())objects
.collect::<Vec<String>>iter()
.joinmap(",|o| "o.to_string());
.collect::<Vec<String>>()
.join(", ")
);
}
 
Line 314 ⟶ 395:
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|" Exits: o{}.to_string())",
.collect::<Vec<String>>()room_exits
.joiniter(", "))
.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() {
Line 328 ⟶ 413:
println!("There is nothing to take here")
} else if args[0] == "all" {
let room_objects = dungeon.rooms.get_mut(&player.location)
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.objects
Line 338 ⟶ 425:
println!("All items taken");
} else if let Some(object) = Object::from_string(args[0]) {
let room_objects = dungeon.rooms.get_mut(&player.location)
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.objects
Line 353 ⟶ 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]) {
if args.is_empty() {
Line 359 ⟶ 449:
println!("You are not carrying anything")
} else if args[0] == "all" {
let room_objects = dungeon.rooms.get_mut(&player.location)
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.objects
Line 369 ⟶ 461:
println!("All items dropped");
} else if let Some(object) = Object::from_string(args[0]) {
let room_objects = dungeon.rooms.get_mut(&player.location)
.rooms
.get_mut(&player.location)
.expect("The player is in a room that should not exist!")
.objects
Line 384 ⟶ 478:
}
 
/// 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()"You are carrying: {}",
.map(|o| o.to_string())player
.collect::<Vec<String>>()inventory
.joiniter(", "));
.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)]
fn dig(player: &Player, dungeon: &mut Dungeon, rng: &mut ThreadRng, args: &[&str]) {
Line 425 ⟶ 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!");
} else {
Line 439 ⟶ 544:
}
 
/// Equips an object
fn equip(player: &mut Player, args: &[&str]) {
if args.is_empty() {
Line 454 ⟶ 560:
}
 
/// Unequips an object
fn unequip(player: &mut Player) {
if player.equipped.is_some() {
Line 463 ⟶ 570:
}
 
/// Main game loop
//////////
// MAIN //
//////////
 
fn main() {
let mut command_aliases = default_aliases();
Line 483 ⟶ 587:
loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Cannot read from stdin");
.read_line(&mut input)
.expect("Cannot read from stdin");
let input: &str = &input.trim().to_lowercase();
 
Line 499 ⟶ 605:
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>
}
 
</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>