RCRPG/Rust: Difference between revisions
Refactoring
Pistacchio (talk | contribs) (Created page with "{{collection|RCRPG}} Rust version of RCRPG. ==Code== <lang rust> use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use std::{io,...") |
Pistacchio (talk | contribs) (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::ops::Add;
use
/// 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>;
/// 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
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
struct Location(i32, i32, i32);
Line 53 ⟶ 49:
}
/// Objects that can be found in the dungon rooms
#[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
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,
}
Line 112 ⟶ 105:
}
/// Sets the room description
fn with_description(mut 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 {
let objects: Vec<_> = vec![
if rng.gen::<f32>() < 0.33 {
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);
Line 134 ⟶ 145:
}
/// Cardinat directions
#[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
/// `(0, -1, 0)` where `(x, y, z)`
fn to_location(self) -> Location {
DIRECTION_MAPPING.iter().find(|d| d.1 ==
}
}
/// Collection of rooms
struct Dungeon {
/// The rooms
rooms: HashMap<Location, Room>,
}
Line 190 ⟶ 200:
Dungeon {
rooms: HashMap::from_iter(vec![
(
(
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
.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
#[derive(Debug, Copy, Clone)]
enum Command {
Line 234 ⟶ 250:
}
/// Returns the list of all the default command aliases
fn default_aliases() -> CommandAliases {
vec![
(
),
(
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!["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 {
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.
.
.
.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!(
.
.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)
.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)
.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)
.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)
.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!(
.
.
.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) {
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
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");
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,
Some(Command::South) => goto(&mut player, &dungeon,
Some(Command::West) => goto(&mut player, &dungeon,
Some(Command::East) => goto(&mut player, &dungeon,
Some(Command::Down) => goto(&mut player, &dungeon,
Some(Command::Up) => goto(&mut player, &dungeon,
_ => println!("I don't know what you mean."),
}
}
}
}</lang>
|