Решение на Dungeons and Compilers от Стоян Грозданов

Обратно към всички решения

Към профила на Стоян Грозданов

Резултати

  • 19 точки от тестове
  • 1 бонус точка
  • 20 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::{collections::HashMap, hash::Hash, io::BufRead, str::FromStr};
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Direction {
North,
South,
East,
West,
}
impl FromStr for Direction {
type Err = Errors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"North" => Ok(Self::North),
"South" => Ok(Self::South),
"East" => Ok(Self::East),
"West" => Ok(Self::West),
_ => Err(Errors::DirectionParseError(String::from(s)))
}
}
}
impl Direction {
pub fn opposite(&self) -> Self {
match self {
Self::North => Self::South,
Self::South => Self::North,
Self::East => Self::West,
Self::West => Self::East
}
}
}
pub struct Room {
pub name: String,
pub neighbours: HashMap<Direction, String>
}
pub struct Dungeon {
pub rooms: HashMap<String, Room>,
}
impl Dungeon {
pub fn new() -> Self {
Dungeon { rooms: HashMap::new() }
}
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
return Err(Errors::DuplicateRoom(String::from(name)));
}
let room = Room { name: String::from(name), neighbours: HashMap::new() };
self.rooms.insert(String::from(name), room );
Ok(())
}
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
match self.rooms.get(room_name) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(String::from(room_name)))
}
}
pub fn set_link(&mut self, room_name: &str, direction: Direction, other_room_name: &str) -> Result<(), Errors> {
self.get_room_mut(room_name)?.neighbours.insert(direction, String::from(other_room_name));
self.get_room_mut(other_room_name)?.neighbours.insert(direction.opposite(), String::from(room_name));
Ok(())
}
pub fn get_next_room(&self, room_name: &str, direction: Direction) -> Result<Option<&Room>, Errors> {
let room = self.get_room(room_name)?;
if let Some(neighbour_name) = room.neighbours.get(&direction) {
Ok(Some(self.get_room(neighbour_name)?))
} else {
Ok(None)
}
}
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut dungeon = Self::new();
let mut lines_iterator = reader.lines().map(|line| line.unwrap());
let mut line_number = 1;
if let Some(line) = lines_iterator.next() {
if line != "## Rooms" {
return Err(Errors::LineParseError { line_number });
}
} else {
return Err(Errors::LineParseError { line_number: 0 }); // Empty Reader
}
line_number += 1;
while let Some(line) = lines_iterator.next() {
if line == "" {
line_number += 1;
break;
}
if !line.starts_with("- ") {
return Err(Errors::LineParseError { line_number });
}
let room_name = line[2..].trim();
dungeon.add_room(room_name)?;
line_number += 1;
}
if let Some(line) = lines_iterator.next() {
if line != "## Links" {
return Err(Errors::LineParseError { line_number });
}
} else {
return Err(Errors::LineParseError { line_number });
}
line_number += 1;
while let Some(line) = lines_iterator.next() {
if !line.starts_with("- ") {
return Err(Errors::LineParseError { line_number });
}
match line[2..].split(" -> ").map(|word| word.trim()).collect::<Vec<&str>>()[..] {
[room_name, direction, other_room_name] => {
dungeon.set_link(room_name, Direction::from_str(direction)?, other_room_name)?;
},
_ => return Err(Errors::LineParseError { line_number })
};
line_number += 1;
}
Ok(dungeon)
}
pub fn find_path(&self, start_room_name: &str, end_room_name: &str) -> Result<Option<Vec<&Room>>, Errors> {
self.get_room(start_room_name)?;
self.get_room(end_room_name)?;
let mut visited_rooms = Vec::new();
let has_path = self.depth_first_search(start_room_name, end_room_name, &mut visited_rooms)?;
if has_path {
Ok(Some(visited_rooms))
} else {
Ok(None)
}
}
// private
fn get_room_mut(&mut self, room_name: &str) -> Result<&mut Room, Errors> {
match self.rooms.get_mut(room_name) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(String::from(room_name)))
}
}
fn depth_first_search<'a>(&'a self, current_room_name: &str, end_room_name: &str, visited_rooms: &mut Vec<&'a Room>) -> Result<bool, Errors> {
if current_room_name == end_room_name {
visited_rooms.push(self.get_room(end_room_name)?);
return Ok(true);
}
if let Some(_) = visited_rooms.iter().find(|room| room.name == current_room_name) {
return Ok(false);
}
let current_room = self.get_room(current_room_name)?;
visited_rooms.push(current_room);
if let Some(north_room) = current_room.neighbours.get(&Direction::North) {
if self.depth_first_search(north_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(south_room) = current_room.neighbours.get(&Direction::South) {
if self.depth_first_search(south_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(east_room) = current_room.neighbours.get(&Direction::East) {
if self.depth_first_search(east_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(west_room) = current_room.neighbours.get(&Direction::West) {
if self.depth_first_search(west_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
visited_rooms.pop();
Ok(false)
}
}
#[cfg(test)]
mod tests {
use std::{str::FromStr, collections::HashMap, io};
use crate::{Dungeon, Direction, Errors};
#[test]
fn it_returns_opposite_direction() {
assert_eq!(Direction::North.opposite(), Direction::South);
assert_eq!(Direction::South.opposite(), Direction::North);
assert_eq!(Direction::East.opposite(), Direction::West);
assert_eq!(Direction::West.opposite(), Direction::East);
}
#[test]
fn it_returns_direction_from_string() {
assert_eq!(Direction::from_str("North").unwrap(), Direction::North);
assert_eq!(Direction::from_str("South").unwrap(), Direction::South);
assert_eq!(Direction::from_str("East").unwrap(), Direction::East);
assert_eq!(Direction::from_str("West").unwrap(), Direction::West);
}
#[test]
fn it_returns_error_on_invalid_direction() {
if let Err(Errors::DirectionParseError(direction)) = Direction::from_str("NorthWest") {
assert_eq!(direction, "NorthWest");
} else {
unreachable!();
}
}
#[test]
fn it_adds_rooms() {
let mut dungeon = Dungeon::new();
dungeon.add_room("room1").unwrap();
dungeon.add_room("room2").unwrap();
dungeon.add_room("room3").unwrap();
assert_eq!(dungeon.rooms.get("room1").unwrap().name, "room1");
assert_eq!(dungeon.rooms.get("room1").unwrap().neighbours, HashMap::new());
assert_eq!(dungeon.rooms.get("room2").unwrap().name, "room2");
assert_eq!(dungeon.rooms.get("room2").unwrap().neighbours, HashMap::new());
assert_eq!(dungeon.rooms.get("room3").unwrap().name, "room3");
assert_eq!(dungeon.rooms.get("room3").unwrap().neighbours, HashMap::new());
}
#[test]
fn it_returns_error_on_duplicate_room() {
let mut dungeon = Dungeon::new();
dungeon.add_room("room1").unwrap();
if let Err(Errors::DuplicateRoom(room_name)) = dungeon.add_room("room1") {
assert_eq!(room_name, "room1");
} else {
unreachable!();
}
}
#[test]
fn it_retrieves_rooms() {
let mut dungeon = Dungeon::new();
dungeon.add_room("room1").unwrap();
dungeon.add_room("room2").unwrap();
let room1 = dungeon.get_room("room1").unwrap();
let room2 = dungeon.get_room("room2").unwrap();
assert_eq!(room1.name, "room1");
assert_eq!(room1.neighbours, HashMap::new());
assert_eq!(room2.name, "room2");
assert_eq!(room2.neighbours, HashMap::new());
}
#[test]
fn it_returns_error_on_unknown_room() {
let dungeon = Dungeon::new();
if let Err(Errors::UnknownRoom(room_name)) = dungeon.get_room("room1") {
assert_eq!(room_name, "room1");
} else {
unreachable!();
}
}
#[test]
fn it_sets_a_room_link() {
let mut dungeon = Dungeon::new();
dungeon.add_room("room1").unwrap();
dungeon.add_room("room2").unwrap();
dungeon.set_link("room1", Direction::North, "room2").unwrap();
let room1 = dungeon.get_room("room1").unwrap();
let room2 = dungeon.get_room("room2").unwrap();
assert_eq!(room1.neighbours.get(&Direction::North).unwrap(), "room2");
assert_eq!(room2.neighbours.get(&Direction::South).unwrap(), "room1");
assert_eq!(room1.neighbours.get(&Direction::East).is_none(), true);
assert_eq!(room1.neighbours.get(&Direction::West).is_none(), true);
assert_eq!(room1.neighbours.get(&Direction::South).is_none(), true);
assert_eq!(room2.neighbours.get(&Direction::East).is_none(), true);
assert_eq!(room2.neighbours.get(&Direction::West).is_none(), true);
assert_eq!(room2.neighbours.get(&Direction::North).is_none(), true);
}
#[test]
fn it_retrieves_a_room_by_direction() {
let mut dungeon = Dungeon::new();
dungeon.add_room("room1").unwrap();
dungeon.add_room("room2").unwrap();
dungeon.set_link("room1", Direction::North, "room2").unwrap();
let room2 = dungeon.get_next_room("room1", Direction::North).unwrap().unwrap();
assert_eq!(room2.name, "room2");
assert_eq!(room2.neighbours.get(&Direction::South).unwrap(), "room1");
let none_room = dungeon.get_next_room("room1", Direction::East).unwrap();
assert_eq!(none_room.is_none(), true);
}
#[test]
fn it_returns_error_on_unknown_room_again() {
let dungeon = Dungeon::new();
if let Err(Errors::UnknownRoom(room_name)) = dungeon.get_next_room("room1", Direction::East) {
assert_eq!(room_name, "room1");
} else {
unreachable!();
}
}
#[test]
fn it_reads_the_data_from_a_reader() {
let reader = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
\n\
## Links\n\
- Entrance -> East -> Hallway\
";
let dungeon = Dungeon::from_reader(reader.as_bytes()).unwrap();
assert_eq!(dungeon.get_room("Entrance").is_ok(), true);
assert_eq!(dungeon.get_room("Hallway").is_ok(), true);
assert_eq!(dungeon.get_room("room1").is_err(), true);
assert_eq!(dungeon.get_room("Entrance").unwrap().neighbours.get(&Direction::East).unwrap(), "Hallway");
assert_eq!(dungeon.get_room("Hallway").unwrap().neighbours.get(&Direction::West).unwrap(), "Entrance");
assert_eq!(dungeon.get_room("Entrance").unwrap().neighbours.get(&Direction::North).is_none(), true);
}
#[test]
fn it_returns_parse_error() {
let bad_reader_1 = "\
## Links\n\
- Entrance -> East -> Hallway\
\n\
## Rooms\n\
- Entrance\n\
- Hallway\n\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_1) {
assert_eq!(line_number, 1);
} else {
unreachable!();
}
let bad_reader_2 = "\
## Rooms\n\
Entrance\n\
- Hallway\n\
\n\
## Links\n\
- Entrance -> East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_2) {
assert_eq!(line_number, 2);
} else {
unreachable!();
}
let bad_reader_3 = "\
## Rooms\n\
- Entrance\n\
Hallway\n\
\n\
## Links\n\
- Entrance -> East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_3) {
assert_eq!(line_number, 3);
} else {
unreachable!();
}
let bad_reader_4 = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
## Links\n\
- Entrance -> East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_4) {
assert_eq!(line_number, 4);
} else {
unreachable!();
}
let bad_reader_5 = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
\n\
- Entrance -> East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_5) {
assert_eq!(line_number, 5);
} else {
unreachable!();
}
let bad_reader_6 = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
\n\
## Links\n\
Entrance -> East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_6) {
assert_eq!(line_number, 6);
} else {
unreachable!();
}
let bad_reader_7 = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
\n\
## Links\n\
- Entrance East -> Hallway\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_7) {
assert_eq!(line_number, 6);
} else {
unreachable!();
}
let bad_reader_8 = "\
## Rooms\n\
- Entrance\n\
- Hallway\n\
\n\
## Links\n\
- Entrance -> East -> Hallway\n\
- Entrance -> East -> Hallway -> West -> Entrance\
".as_bytes();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_8) {
assert_eq!(line_number, 7);
} else {
unreachable!();
}
let bad_reader_9 = "\
## Rooms\n\
- Hallway\n\
\n\
## Links\n\
- Entrance -> East -> Hallway\n\
".as_bytes();
if let Err(Errors::UnknownRoom(room_name)) = Dungeon::from_reader(bad_reader_9) {
assert_eq!(room_name, "Entrance");
} else {
unreachable!();
}
let bad_reader_10 = "\
## Rooms\n\
- Hallway\n\
- Entrance\n\
\n\
## Links\n\
- Entrance -> Zapad -> Hallway\n\
".as_bytes();
if let Err(Errors::DirectionParseError(direction)) = Dungeon::from_reader(bad_reader_10) {
assert_eq!(direction, "Zapad");
} else {
unreachable!();
}
let bad_reader_11 = io::empty();
if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_11) {
assert_eq!(line_number, 0);
} else {
unreachable!();
}
}
#[test]
fn it_finds_the_room_path() {
let mut dungeon = Dungeon::new();
dungeon.add_room("A").unwrap();
dungeon.add_room("B").unwrap();
dungeon.add_room("C").unwrap();
dungeon.add_room("D").unwrap();
dungeon.add_room("E").unwrap();
dungeon.add_room("F").unwrap();
dungeon.add_room("G").unwrap();
dungeon.set_link("A", Direction::North, "B").unwrap();
dungeon.set_link("B", Direction::East, "C").unwrap();
dungeon.set_link("C", Direction::South, "D").unwrap();
dungeon.set_link("D", Direction::West, "A").unwrap();
dungeon.set_link("A", Direction::South, "E").unwrap();
dungeon.set_link("F", Direction::South, "G").unwrap();
let path_1: Vec<&str> = dungeon.find_path("A", "A").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
let path_2: Vec<&str> = dungeon.find_path("A", "D").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
let path_3: Vec<&str> = dungeon.find_path("A", "E").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
let path_4: Vec<&str> = dungeon.find_path("C", "B").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
let path_5: Vec<&str> = dungeon.find_path("B", "E").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
let none_path_1 = dungeon.find_path("A", "F").unwrap();
let none_path_2 = dungeon.find_path("F", "A").unwrap();
let error_path_1 = dungeon.find_path("A", "H");
let error_path_2 = dungeon.find_path("Z", "A");
let error_path_3 = dungeon.find_path("X", "V");
assert_eq!(path_1, &["A"]);
assert_eq!(path_2, &["A", "B", "C", "D"]);
assert_eq!(path_3, &["A", "E"]);
assert_eq!(path_4, &["C", "D", "A", "B"]);
assert_eq!(path_5, &["B", "A", "E"]);
assert_eq!(none_path_1.is_none(), true);
assert_eq!(none_path_2.is_none(), true);
if let Err(Errors::UnknownRoom(room_name)) = error_path_1 {
assert_eq!(room_name, "H");
} else {
unreachable!();
}
if let Err(Errors::UnknownRoom(room_name)) = error_path_2 {
assert_eq!(room_name, "Z");
} else {
unreachable!();
}
if let Err(Errors::UnknownRoom(room_name)) = error_path_3 {
assert_eq!(room_name, "X");
} else {
unreachable!();
}
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20220116-3533338-3r64t4/solution)
    Finished test [unoptimized + debuginfo] target(s) in 3.58s
     Running tests/solution_test.rs (target/debug/deps/solution_test-2e292b23ac75572c)

running 15 tests
test solution_test::test_adding_rooms_1 ... ok
test solution_test::test_adding_rooms_2 ... ok
test solution_test::test_cyrillic_room_names ... ok
test solution_test::test_finding_a_direct_path ... ok
test solution_test::test_finding_a_reflexive_path ... ok
test solution_test::test_finding_an_indirect_path ... ok
test solution_test::test_finding_no_path ... ok
test solution_test::test_invalid_parsing ... ok
test solution_test::test_io_error ... FAILED
test solution_test::test_overwriting_a_room_link ... ok
test solution_test::test_parsing_cyrillic_rooms ... ok
test solution_test::test_parsing_no_rooms_or_links ... ok
test solution_test::test_parsing_rooms ... ok
test solution_test::test_room_errors ... ok
test solution_test::test_room_links ... ok

failures:

---- solution_test::test_io_error stdout ----
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "fill_buf error!" }', /tmp/d20220116-3533338-3r64t4/solution/src/lib.rs:97:65
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "fill_buf error!" }', tests/solution_test.rs:194:5


failures:
    solution_test::test_io_error

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--test solution_test'

История (2 версии и 0 коментара)

Стоян качи първо решение на 06.01.2022 00:52 (преди почти 4 години)

Стоян качи решение на 06.01.2022 23:19 (преди почти 4 години)

-use std::{collections::HashMap, hash::Hash, io::BufRead};
+use std::{collections::HashMap, hash::Hash, io::BufRead, str::FromStr};
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Direction {
North,
South,
East,
West,
}
+impl FromStr for Direction {
+ type Err = Errors;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "North" => Ok(Self::North),
+ "South" => Ok(Self::South),
+ "East" => Ok(Self::East),
+ "West" => Ok(Self::West),
+ _ => Err(Errors::DirectionParseError(String::from(s)))
+ }
+ }
+}
+
impl Direction {
pub fn opposite(&self) -> Self {
match self {
Self::North => Self::South,
Self::South => Self::North,
Self::East => Self::West,
Self::West => Self::East
}
}
-
- pub fn from_str(direction: &str) -> Result<Self, Errors> {
- match direction {
- "North" => Ok(Self::North),
- "South" => Ok(Self::South),
- "East" => Ok(Self::East),
- "West" => Ok(Self::West),
- _ => Err(Errors::DirectionParseError(String::from(direction)))
- }
- }
}
-#[derive(Debug)]
pub struct Room {
pub name: String,
pub neighbours: HashMap<Direction, String>
}
pub struct Dungeon {
pub rooms: HashMap<String, Room>,
}
impl Dungeon {
pub fn new() -> Self {
Dungeon { rooms: HashMap::new() }
}
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
return Err(Errors::DuplicateRoom(String::from(name)));
}
let room = Room { name: String::from(name), neighbours: HashMap::new() };
self.rooms.insert(String::from(name), room );
Ok(())
}
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
match self.rooms.get(room_name) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(String::from(room_name)))
}
}
pub fn set_link(&mut self, room_name: &str, direction: Direction, other_room_name: &str) -> Result<(), Errors> {
- self.get_mut_room(room_name)?.neighbours.insert(direction, String::from(other_room_name));
- self.get_mut_room(other_room_name)?.neighbours.insert(direction.opposite(), String::from(room_name));
+ self.get_room_mut(room_name)?.neighbours.insert(direction, String::from(other_room_name));
+ self.get_room_mut(other_room_name)?.neighbours.insert(direction.opposite(), String::from(room_name));
Ok(())
}
pub fn get_next_room(&self, room_name: &str, direction: Direction) -> Result<Option<&Room>, Errors> {
let room = self.get_room(room_name)?;
if let Some(neighbour_name) = room.neighbours.get(&direction) {
Ok(Some(self.get_room(neighbour_name)?))
} else {
Ok(None)
}
}
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut dungeon = Self::new();
let mut lines_iterator = reader.lines().map(|line| line.unwrap());
let mut line_number = 1;
if let Some(line) = lines_iterator.next() {
if line != "## Rooms" {
return Err(Errors::LineParseError { line_number });
}
} else {
return Err(Errors::LineParseError { line_number: 0 }); // Empty Reader
}
line_number += 1;
while let Some(line) = lines_iterator.next() {
if line == "" {
+ line_number += 1;
+
break;
}
if !line.starts_with("- ") {
return Err(Errors::LineParseError { line_number });
}
let room_name = line[2..].trim();
dungeon.add_room(room_name)?;
line_number += 1;
}
- line_number += 1;
-
if let Some(line) = lines_iterator.next() {
if line != "## Links" {
return Err(Errors::LineParseError { line_number });
}
} else {
- return Err(Errors::LineParseError { line_number }); // Empty Reader
+ return Err(Errors::LineParseError { line_number });
}
line_number += 1;
while let Some(line) = lines_iterator.next() {
if !line.starts_with("- ") {
return Err(Errors::LineParseError { line_number });
}
match line[2..].split(" -> ").map(|word| word.trim()).collect::<Vec<&str>>()[..] {
[room_name, direction, other_room_name] => {
dungeon.set_link(room_name, Direction::from_str(direction)?, other_room_name)?;
},
_ => return Err(Errors::LineParseError { line_number })
};
line_number += 1;
}
Ok(dungeon)
}
pub fn find_path(&self, start_room_name: &str, end_room_name: &str) -> Result<Option<Vec<&Room>>, Errors> {
+ self.get_room(start_room_name)?;
+ self.get_room(end_room_name)?;
+
let mut visited_rooms = Vec::new();
- self.depth_first_search(start_room_name, end_room_name, &mut visited_rooms)?;
+ let has_path = self.depth_first_search(start_room_name, end_room_name, &mut visited_rooms)?;
- if visited_rooms.is_empty() {
- Ok(None)
- } else {
+ if has_path {
Ok(Some(visited_rooms))
+ } else {
+ Ok(None)
}
}
// private
- fn get_mut_room(&mut self, room_name: &str) -> Result<&mut Room, Errors> {
+ fn get_room_mut(&mut self, room_name: &str) -> Result<&mut Room, Errors> {
match self.rooms.get_mut(room_name) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(String::from(room_name)))
}
}
fn depth_first_search<'a>(&'a self, current_room_name: &str, end_room_name: &str, visited_rooms: &mut Vec<&'a Room>) -> Result<bool, Errors> {
if current_room_name == end_room_name {
visited_rooms.push(self.get_room(end_room_name)?);
return Ok(true);
}
+ if let Some(_) = visited_rooms.iter().find(|room| room.name == current_room_name) {
+ return Ok(false);
+ }
+
let current_room = self.get_room(current_room_name)?;
visited_rooms.push(current_room);
if let Some(north_room) = current_room.neighbours.get(&Direction::North) {
if self.depth_first_search(north_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(south_room) = current_room.neighbours.get(&Direction::South) {
if self.depth_first_search(south_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(east_room) = current_room.neighbours.get(&Direction::East) {
if self.depth_first_search(east_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
if let Some(west_room) = current_room.neighbours.get(&Direction::West) {
if self.depth_first_search(west_room, end_room_name, visited_rooms)? {
return Ok(true);
}
}
visited_rooms.pop();
Ok(false)
}
-}
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{str::FromStr, collections::HashMap, io};
+ use crate::{Dungeon, Direction, Errors};
+
+ #[test]
+ fn it_returns_opposite_direction() {
+ assert_eq!(Direction::North.opposite(), Direction::South);
+ assert_eq!(Direction::South.opposite(), Direction::North);
+ assert_eq!(Direction::East.opposite(), Direction::West);
+ assert_eq!(Direction::West.opposite(), Direction::East);
+ }
+
+ #[test]
+ fn it_returns_direction_from_string() {
+ assert_eq!(Direction::from_str("North").unwrap(), Direction::North);
+ assert_eq!(Direction::from_str("South").unwrap(), Direction::South);
+ assert_eq!(Direction::from_str("East").unwrap(), Direction::East);
+ assert_eq!(Direction::from_str("West").unwrap(), Direction::West);
+ }
+
+ #[test]
+ fn it_returns_error_on_invalid_direction() {
+ if let Err(Errors::DirectionParseError(direction)) = Direction::from_str("NorthWest") {
+ assert_eq!(direction, "NorthWest");
+ } else {
+ unreachable!();
+ }
+ }
+
+ #[test]
+ fn it_adds_rooms() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("room1").unwrap();
+ dungeon.add_room("room2").unwrap();
+ dungeon.add_room("room3").unwrap();
+
+ assert_eq!(dungeon.rooms.get("room1").unwrap().name, "room1");
+ assert_eq!(dungeon.rooms.get("room1").unwrap().neighbours, HashMap::new());
+
+ assert_eq!(dungeon.rooms.get("room2").unwrap().name, "room2");
+ assert_eq!(dungeon.rooms.get("room2").unwrap().neighbours, HashMap::new());
+
+ assert_eq!(dungeon.rooms.get("room3").unwrap().name, "room3");
+ assert_eq!(dungeon.rooms.get("room3").unwrap().neighbours, HashMap::new());
+ }
+
+ #[test]
+ fn it_returns_error_on_duplicate_room() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("room1").unwrap();
+
+ if let Err(Errors::DuplicateRoom(room_name)) = dungeon.add_room("room1") {
+ assert_eq!(room_name, "room1");
+ } else {
+ unreachable!();
+ }
+ }
+
+ #[test]
+ fn it_retrieves_rooms() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("room1").unwrap();
+ dungeon.add_room("room2").unwrap();
+
+ let room1 = dungeon.get_room("room1").unwrap();
+ let room2 = dungeon.get_room("room2").unwrap();
+
+ assert_eq!(room1.name, "room1");
+ assert_eq!(room1.neighbours, HashMap::new());
+
+ assert_eq!(room2.name, "room2");
+ assert_eq!(room2.neighbours, HashMap::new());
+ }
+
+ #[test]
+ fn it_returns_error_on_unknown_room() {
+ let dungeon = Dungeon::new();
+
+ if let Err(Errors::UnknownRoom(room_name)) = dungeon.get_room("room1") {
+ assert_eq!(room_name, "room1");
+ } else {
+ unreachable!();
+ }
+ }
+
+ #[test]
+ fn it_sets_a_room_link() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("room1").unwrap();
+ dungeon.add_room("room2").unwrap();
+
+ dungeon.set_link("room1", Direction::North, "room2").unwrap();
+
+ let room1 = dungeon.get_room("room1").unwrap();
+ let room2 = dungeon.get_room("room2").unwrap();
+
+ assert_eq!(room1.neighbours.get(&Direction::North).unwrap(), "room2");
+ assert_eq!(room2.neighbours.get(&Direction::South).unwrap(), "room1");
+
+ assert_eq!(room1.neighbours.get(&Direction::East).is_none(), true);
+ assert_eq!(room1.neighbours.get(&Direction::West).is_none(), true);
+ assert_eq!(room1.neighbours.get(&Direction::South).is_none(), true);
+
+ assert_eq!(room2.neighbours.get(&Direction::East).is_none(), true);
+ assert_eq!(room2.neighbours.get(&Direction::West).is_none(), true);
+ assert_eq!(room2.neighbours.get(&Direction::North).is_none(), true);
+ }
+
+ #[test]
+ fn it_retrieves_a_room_by_direction() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("room1").unwrap();
+ dungeon.add_room("room2").unwrap();
+
+ dungeon.set_link("room1", Direction::North, "room2").unwrap();
+
+ let room2 = dungeon.get_next_room("room1", Direction::North).unwrap().unwrap();
+
+ assert_eq!(room2.name, "room2");
+ assert_eq!(room2.neighbours.get(&Direction::South).unwrap(), "room1");
+
+ let none_room = dungeon.get_next_room("room1", Direction::East).unwrap();
+ assert_eq!(none_room.is_none(), true);
+ }
+
+ #[test]
+ fn it_returns_error_on_unknown_room_again() {
+ let dungeon = Dungeon::new();
+
+ if let Err(Errors::UnknownRoom(room_name)) = dungeon.get_next_room("room1", Direction::East) {
+ assert_eq!(room_name, "room1");
+ } else {
+ unreachable!();
+ }
+ }
+
+ #[test]
+ fn it_reads_the_data_from_a_reader() {
+ let reader = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\
+ ";
+
+ let dungeon = Dungeon::from_reader(reader.as_bytes()).unwrap();
+
+ assert_eq!(dungeon.get_room("Entrance").is_ok(), true);
+ assert_eq!(dungeon.get_room("Hallway").is_ok(), true);
+ assert_eq!(dungeon.get_room("room1").is_err(), true);
+
+ assert_eq!(dungeon.get_room("Entrance").unwrap().neighbours.get(&Direction::East).unwrap(), "Hallway");
+ assert_eq!(dungeon.get_room("Hallway").unwrap().neighbours.get(&Direction::West).unwrap(), "Entrance");
+ assert_eq!(dungeon.get_room("Entrance").unwrap().neighbours.get(&Direction::North).is_none(), true);
+ }
+
+ #[test]
+ fn it_returns_parse_error() {
+ let bad_reader_1 = "\
+ ## Links\n\
+ - Entrance -> East -> Hallway\
+ \n\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_1) {
+ assert_eq!(line_number, 1);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_2 = "\
+ ## Rooms\n\
+ Entrance\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_2) {
+ assert_eq!(line_number, 2);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_3 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_3) {
+ assert_eq!(line_number, 3);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_4 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_4) {
+ assert_eq!(line_number, 4);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_5 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ \n\
+ - Entrance -> East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_5) {
+ assert_eq!(line_number, 5);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_6 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ Entrance -> East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_6) {
+ assert_eq!(line_number, 6);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_7 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance East -> Hallway\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_7) {
+ assert_eq!(line_number, 6);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_8 = "\
+ ## Rooms\n\
+ - Entrance\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\n\
+ - Entrance -> East -> Hallway -> West -> Entrance\
+ ".as_bytes();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_8) {
+ assert_eq!(line_number, 7);
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_9 = "\
+ ## Rooms\n\
+ - Hallway\n\
+ \n\
+ ## Links\n\
+ - Entrance -> East -> Hallway\n\
+ ".as_bytes();
+
+ if let Err(Errors::UnknownRoom(room_name)) = Dungeon::from_reader(bad_reader_9) {
+ assert_eq!(room_name, "Entrance");
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_10 = "\
+ ## Rooms\n\
+ - Hallway\n\
+ - Entrance\n\
+ \n\
+ ## Links\n\
+ - Entrance -> Zapad -> Hallway\n\
+ ".as_bytes();
+
+ if let Err(Errors::DirectionParseError(direction)) = Dungeon::from_reader(bad_reader_10) {
+ assert_eq!(direction, "Zapad");
+ } else {
+ unreachable!();
+ }
+
+ let bad_reader_11 = io::empty();
+
+ if let Err(Errors::LineParseError { line_number }) = Dungeon::from_reader(bad_reader_11) {
+ assert_eq!(line_number, 0);
+ } else {
+ unreachable!();
+ }
+ }
+
+ #[test]
+ fn it_finds_the_room_path() {
+ let mut dungeon = Dungeon::new();
+
+ dungeon.add_room("A").unwrap();
+ dungeon.add_room("B").unwrap();
+ dungeon.add_room("C").unwrap();
+ dungeon.add_room("D").unwrap();
+ dungeon.add_room("E").unwrap();
+ dungeon.add_room("F").unwrap();
+ dungeon.add_room("G").unwrap();
+
+ dungeon.set_link("A", Direction::North, "B").unwrap();
+ dungeon.set_link("B", Direction::East, "C").unwrap();
+ dungeon.set_link("C", Direction::South, "D").unwrap();
+ dungeon.set_link("D", Direction::West, "A").unwrap();
+ dungeon.set_link("A", Direction::South, "E").unwrap();
+ dungeon.set_link("F", Direction::South, "G").unwrap();
+
+ let path_1: Vec<&str> = dungeon.find_path("A", "A").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
+ let path_2: Vec<&str> = dungeon.find_path("A", "D").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
+ let path_3: Vec<&str> = dungeon.find_path("A", "E").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
+ let path_4: Vec<&str> = dungeon.find_path("C", "B").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
+ let path_5: Vec<&str> = dungeon.find_path("B", "E").unwrap().unwrap().iter().map(|room| room.name.as_str()).collect();
+ let none_path_1 = dungeon.find_path("A", "F").unwrap();
+ let none_path_2 = dungeon.find_path("F", "A").unwrap();
+ let error_path_1 = dungeon.find_path("A", "H");
+ let error_path_2 = dungeon.find_path("Z", "A");
+ let error_path_3 = dungeon.find_path("X", "V");
+
+ assert_eq!(path_1, &["A"]);
+ assert_eq!(path_2, &["A", "B", "C", "D"]);
+ assert_eq!(path_3, &["A", "E"]);
+ assert_eq!(path_4, &["C", "D", "A", "B"]);
+ assert_eq!(path_5, &["B", "A", "E"]);
+ assert_eq!(none_path_1.is_none(), true);
+ assert_eq!(none_path_2.is_none(), true);
+
+ if let Err(Errors::UnknownRoom(room_name)) = error_path_1 {
+ assert_eq!(room_name, "H");
+ } else {
+ unreachable!();
+ }
+
+ if let Err(Errors::UnknownRoom(room_name)) = error_path_2 {
+ assert_eq!(room_name, "Z");
+ } else {
+ unreachable!();
+ }
+
+ if let Err(Errors::UnknownRoom(room_name)) = error_path_3 {
+ assert_eq!(room_name, "X");
+ } else {
+ unreachable!();
+ }
+ }
+}