Решение на Dungeons and Compilers от Петко Каменов

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

Към профила на Петко Каменов

Резултати

  • 17 точки от тестове
  • 1 бонус точка
  • 18 точки общо
  • 13 успешни тест(а)
  • 2 неуспешни тест(а)

Код

use std::{
collections::{HashMap, HashSet, VecDeque},
fs::File,
io::{BufRead, BufReader},
rc::Rc,
};
/// Различните грешки, които ще очакваме да върнете като резултат от някои невалидни операции.
/// Повече детайли по-долу.
///
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
impl Direction {
pub fn opposite(&self) -> Direction {
match self {
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::East => Direction::West,
Direction::West => Direction::East,
}
}
pub fn iter() -> impl Iterator<Item = Direction> {
[
Direction::North,
Direction::South,
Direction::East,
Direction::West,
]
.iter()
.cloned()
}
}
/// Една стая в подземията. Дефинира се само с име, макар че в по-интересна имплементация може да
/// държи item-и, противници...
///
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Room {
pub name: String,
pub connections: HashMap<Direction, String>,
}
/// Контейнер за стаите и не само. Ще работим предимно със тази структура.
///
pub struct Dungeon {
pub rooms: HashMap<String, Rc<Room>>,
}
impl Dungeon {
/// Конструиране на празен Dungeon, в който няма никакви стаи.
///
pub fn new() -> Self {
Dungeon {
rooms: HashMap::new(),
}
}
/// Добавяне на стая към Dungeon с име `name`. Връща `Ok(())` при успех. Ако вече има стая с
/// такова име, очакваме да върнете `Errors::DuplicateRoom` с името.
///
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
Err(Errors::DuplicateRoom(name.to_string()))
} else {
self.rooms.insert(
name.to_string(),
Rc::new(Room {
name: name.to_string(),
connections: HashMap::new(),
}),
);
Ok(())
}
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
if let Some(room) = self.rooms.get(room_name) {
Ok(room)
} else {
Err(Errors::UnknownRoom(room_name.to_string()))
}
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `room_name` да има връзка в посока `direction` със стаята с име `other_room_name`.
///
/// Също така очакваме `other_room_name` да има връзка с `room_name` в *обратната* посока.
///
/// Успешен резултат е `Ok(())`. В случай, че която от двете стаи не същестува, очакваме грешка
/// `Errors::UnknownRoom` със съответното име на липсваща стая. Ако и двете липсват, спокойно
/// върнете тази, която проверявате първо.
///
pub fn set_link(
&mut self,
room_name: &str,
direction: Direction,
other_room_name: &str,
) -> Result<(), Errors> {
if !self.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
if !self.rooms.contains_key(other_room_name) {
return Err(Errors::UnknownRoom(other_room_name.to_string()));
}
let room = Rc::get_mut(self.rooms.get_mut(room_name).unwrap()).unwrap();
room.connections
.insert(direction, other_room_name.to_string());
let other_room = Rc::get_mut(self.rooms.get_mut(other_room_name).unwrap()).unwrap();
other_room
.connections
.insert(direction.opposite(), room_name.to_string());
Ok(())
}
/// Четене на съседа на стаята с име `room_name` в посока `direction`. Тук има няколко
/// варианта на изход:
///
/// - Ако подадената стая не съществува, очакваме грешка `Errors::UnknownRoom`
/// - Ако подадената стая няма съсед в тази посока, Ok(None) е смисления резултат
/// - Иначе, чакаме reference към `Room` структурата на въпросния съсед, опакована в `Ok(Some(`.
///
pub fn get_next_room(
&self,
room_name: &str,
direction: Direction,
) -> Result<Option<&Room>, Errors> {
if !self.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
let room = self.rooms.get(room_name).unwrap();
if let Some(next_room_name) = room.connections.get(&direction) {
Ok(Some(self.rooms.get(next_room_name).unwrap()))
} else {
Ok(None)
}
}
/// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
/// файл, или, ако тестваме, може да е просто колекция от байтове.
///
/// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
///
/// Вижте по-долу за обяснение на грешките, които очакваме.
///
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut dungeon = Dungeon::new();
let mut line_count = 0;
let mut parsing = Parsing::Room;
let mut next_line_links = false;
for line in reader.lines() {
if line.is_err() {
return Err(Errors::IoError(line.err().unwrap()));
}
let line = line.unwrap();
line_count += 1;
match parsing {
Parsing::Room => {
if line_count == 1 && line != "## Rooms" {
return Err(Errors::LineParseError { line_number: 1 });
} else if line_count == 1 && line == "## Rooms" {
continue;
} else if line.is_empty() && line_count != 1 {
parsing = Parsing::Links;
next_line_links = true;
} else if let Some(room_name) = match_prefix("- ", &line) {
dungeon.add_room(room_name)?;
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
}
Parsing::Links => {
if next_line_links && line != "## Links" {
return Err(Errors::LineParseError {
line_number: line_count,
});
} else if next_line_links && line == "## Links" {
next_line_links = false;
} else if let Some(link_info) = match_prefix("- ", &line) {
let parsed_link = match_link(link_info);
if let Some(Link {
from,
direction,
to,
}) = parsed_link
{
dungeon.set_link(&from, direction, &to)?;
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
}
}
}
if line_count == 0 {
Err(Errors::LineParseError { line_number: 0 })
} else {
Ok(dungeon)
}
}
pub fn read_from_file(filename: &str) -> Result<Self, Errors> {
let file = File::open(filename).unwrap();
let reader = BufReader::new(file);
Dungeon::from_reader(reader)
}
}
/// match_prefix("- ", "- Foo") //=> Some("Foo")
/// match_prefix("- ", "Bar") //=> None
///
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
if input.starts_with(prefix) {
Some(&input[prefix.len()..])
} else {
None
}
}
pub struct Link {
from: String,
to: String,
direction: Direction,
}
fn match_link<'b>(input: &'b str) -> Option<Link> {
let mut link = Link {
from: String::new(),
to: String::new(),
direction: Direction::North,
};
let mut iter = input.split_terminator(" -> ");
link.from = iter.next().unwrap().to_string();
link.direction = match iter.next().unwrap() {
"North" => Direction::North,
"South" => Direction::South,
"East" => Direction::East,
"West" => Direction::West,
_ => return None,
};
link.to = iter.next().unwrap().to_string();
Some(link)
}
enum Parsing {
Room,
Links,
}
impl Dungeon {
/// Търси път от `start_room_name` до `end_room_name` и го връща във вектор, пакетиран във
/// `Ok(Some(` ако намери.
///
/// Ако няма път между тези две стаи, връща `Ok(None)`.
///
/// Ако четенето на стаи в един момент върне грешка, очакваме да върнете грешката нагоре.
///
pub fn find_path(
&self,
start_room_name: &str,
end_room_name: &str,
) -> Result<Option<Vec<&Room>>, Errors> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut parents = HashMap::new();
queue.push_front(start_room_name);
visited.insert(start_room_name);
parents.insert(start_room_name, None);
while let Some(room_name) = queue.pop_back() {
if room_name == end_room_name {
let mut path = Vec::new();
let mut current_room = room_name;
while let Some(parent) = parents.get(current_room) {
if !parent.is_none() {
path.push(self.rooms.get(current_room).unwrap().as_ref());
current_room = parent.unwrap();
} else {
break;
}
}
path.push(self.rooms.get(start_room_name).unwrap().as_ref());
path.reverse();
return Ok(Some(path));
}
for direction in Direction::iter() {
if let Ok(Some(neigh)) = self.get_next_room(room_name, direction) {
let neigh_name = neigh.name.as_str();
if !visited.contains(&neigh_name) {
queue.push_front(neigh_name);
visited.insert(neigh_name);
parents.insert(neigh_name, Some(room_name));
}
}
}
}
Ok(None)
}
}
#[test]
fn create_empty_dungeon() {
let dungeon = Dungeon::new();
assert_eq!(dungeon.rooms.len(), 0);
}
#[test]
fn add_room_to_empty_dungeon() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Room 1").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
dungeon.add_room("Room 2").unwrap();
assert_eq!(dungeon.rooms.len(), 2);
}
#[test]
fn duplicate_room() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Room 1").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
dungeon.add_room("Room 1").unwrap_err();
assert_eq!(dungeon.rooms.len(), 1);
}
#[test]
fn add_connection_and_get_room() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(
dungeon
.get_next_room("Hallway", Direction::West)
.unwrap()
.unwrap()
.name,
"Entrance"
);
}
#[test]
fn override_connection() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon.add_room("Magic Lab").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
dungeon
.set_link("Hallway", Direction::West, "Magic Lab")
.unwrap();
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
assert_eq!(
dungeon
.get_next_room("Hallway", Direction::West)
.unwrap()
.unwrap()
.name,
"Magic Lab"
);
}
#[test]
fn get_room_not_found() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_next_room("Entrance", Direction::West).unwrap(), None);
}
#[test]
fn concet_one_room_with_self() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Entrance").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Entrance");
assert_eq!(dungeon.get_next_room("Entrance", Direction::West).unwrap().unwrap().name, "Entrance");
}
#[test]
fn test_path_cycle() {
let mut dungeon = Dungeon::new();
dungeon.add_room("A").unwrap();
dungeon.set_link("A", Direction::West, "A").unwrap();
let path = dungeon.find_path("A","A").unwrap().unwrap();
assert_eq!(path.len(),1);
assert_eq!(path[0].name,"A");
}
#[test]
#[should_panic]
fn test_read_error() {
Dungeon::read_from_file("tests/test_dungeon_error.txt").err().unwrap();
}
#[test]
fn test_basic_1() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon.set_link("Entrance", Direction::East, "Hallway").unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
}
const TEST_INPUT_1: &str = "
## Rooms
- Entrance
- Hallway
## Links
- Entrance -> East -> Hallway
";
#[test]
fn test_basic_2() {
// .trim() за да премахнем първия и последния ред:
let dungeon = Dungeon::from_reader(TEST_INPUT_1.trim().as_bytes()).unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
}
#[test]
fn test_basic_3() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Treasure Room").unwrap();
dungeon.set_link("Entrance", Direction::West, "Treasure Room").unwrap();
let path = dungeon.find_path("Entrance", "Treasure Room").unwrap().unwrap();
assert!(path.len() > 0);
}

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

Compiling solution v0.1.0 (/tmp/d20220116-3533338-18ici8s/solution)
warning: constant is never used: `TEST_INPUT_1`
   --> src/lib.rs:465:1
    |
465 | / const TEST_INPUT_1: &str = "
466 | | ## Rooms
467 | | - Entrance
468 | | - Hallway
...   |
471 | | - Entrance -> East -> Hallway
472 | | ";
    | |__^
    |
    = note: `#[warn(dead_code)]` on by default

warning: `solution` (lib) generated 1 warning
    Finished test [unoptimized + debuginfo] target(s) in 3.84s
     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 ... FAILED
test solution_test::test_invalid_parsing ... FAILED
test solution_test::test_io_error ... ok
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_finding_no_path stdout ----
thread '<unnamed>' panicked at 'assertion failed: path.is_err()', tests/solution_test.rs:382:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:372:5

---- solution_test::test_invalid_parsing stdout ----
thread 'main' panicked at 'assertion failed: matches!(Dungeon :: from_reader(TEST_INPUT_7.trim().as_bytes()),\n         Err(Errors :: DirectionParseError(_)))', tests/solution_test.rs:280:5


failures:
    solution_test::test_finding_no_path
    solution_test::test_invalid_parsing

test result: FAILED. 13 passed; 2 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 10:55 (преди почти 4 години)

Петко качи решение на 06.01.2022 11:04 (преди почти 4 години)

use std::{
collections::{HashMap, HashSet, VecDeque},
fs::File,
io::{BufRead, BufReader},
rc::Rc,
};
/// Различните грешки, които ще очакваме да върнете като резултат от някои невалидни операции.
/// Повече детайли по-долу.
///
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
impl Direction {
pub fn opposite(&self) -> Direction {
match self {
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::East => Direction::West,
Direction::West => Direction::East,
}
}
pub fn iter() -> impl Iterator<Item = Direction> {
[
Direction::North,
Direction::South,
Direction::East,
Direction::West,
]
.iter()
.cloned()
}
}
/// Една стая в подземията. Дефинира се само с име, макар че в по-интересна имплементация може да
/// държи item-и, противници...
///
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Room {
pub name: String,
pub connections: HashMap<Direction, String>,
}
/// Контейнер за стаите и не само. Ще работим предимно със тази структура.
///
pub struct Dungeon {
pub rooms: HashMap<String, Rc<Room>>,
}
impl Dungeon {
/// Конструиране на празен Dungeon, в който няма никакви стаи.
///
pub fn new() -> Self {
Dungeon {
rooms: HashMap::new(),
}
}
/// Добавяне на стая към Dungeon с име `name`. Връща `Ok(())` при успех. Ако вече има стая с
/// такова име, очакваме да върнете `Errors::DuplicateRoom` с името.
///
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
Err(Errors::DuplicateRoom(name.to_string()))
} else {
self.rooms.insert(
name.to_string(),
Rc::new(Room {
name: name.to_string(),
connections: HashMap::new(),
}),
);
Ok(())
}
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
if let Some(room) = self.rooms.get(room_name) {
Ok(room)
} else {
Err(Errors::UnknownRoom(room_name.to_string()))
}
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `room_name` да има връзка в посока `direction` със стаята с име `other_room_name`.
///
/// Също така очакваме `other_room_name` да има връзка с `room_name` в *обратната* посока.
///
/// Успешен резултат е `Ok(())`. В случай, че която от двете стаи не същестува, очакваме грешка
/// `Errors::UnknownRoom` със съответното име на липсваща стая. Ако и двете липсват, спокойно
/// върнете тази, която проверявате първо.
///
pub fn set_link(
&mut self,
room_name: &str,
direction: Direction,
other_room_name: &str,
) -> Result<(), Errors> {
if !self.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
if !self.rooms.contains_key(other_room_name) {
return Err(Errors::UnknownRoom(other_room_name.to_string()));
}
let room = Rc::get_mut(self.rooms.get_mut(room_name).unwrap()).unwrap();
room.connections
.insert(direction, other_room_name.to_string());
let other_room = Rc::get_mut(self.rooms.get_mut(other_room_name).unwrap()).unwrap();
other_room
.connections
.insert(direction.opposite(), room_name.to_string());
Ok(())
}
/// Четене на съседа на стаята с име `room_name` в посока `direction`. Тук има няколко
/// варианта на изход:
///
/// - Ако подадената стая не съществува, очакваме грешка `Errors::UnknownRoom`
/// - Ако подадената стая няма съсед в тази посока, Ok(None) е смисления резултат
/// - Иначе, чакаме reference към `Room` структурата на въпросния съсед, опакована в `Ok(Some(`.
///
pub fn get_next_room(
&self,
room_name: &str,
direction: Direction,
) -> Result<Option<&Room>, Errors> {
if !self.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
let room = self.rooms.get(room_name).unwrap();
if let Some(next_room_name) = room.connections.get(&direction) {
Ok(Some(self.rooms.get(next_room_name).unwrap()))
} else {
Ok(None)
}
}
/// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
/// файл, или, ако тестваме, може да е просто колекция от байтове.
///
/// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
///
/// Вижте по-долу за обяснение на грешките, които очакваме.
///
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut dungeon = Dungeon::new();
let mut line_count = 0;
let mut parsing = Parsing::Room;
let mut next_line_links = false;
for line in reader.lines() {
if line.is_err() {
return Err(Errors::IoError(line.err().unwrap()));
}
let line = line.unwrap();
line_count += 1;
match parsing {
Parsing::Room => {
if line_count == 1 && line != "## Rooms" {
return Err(Errors::LineParseError { line_number: 1 });
} else if line_count == 1 && line == "## Rooms" {
continue;
} else if line.is_empty() && line_count != 1 {
parsing = Parsing::Links;
next_line_links = true;
} else if let Some(room_name) = match_prefix("- ", &line) {
dungeon.add_room(room_name)?;
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
}
Parsing::Links => {
if next_line_links && line != "## Links" {
return Err(Errors::LineParseError {
line_number: line_count,
});
} else if next_line_links && line == "## Links" {
next_line_links = false;
} else if let Some(link_info) = match_prefix("- ", &line) {
let parsed_link = match_link(link_info);
if let Some(Link {
from,
direction,
to,
}) = parsed_link
{
dungeon.set_link(&from, direction, &to)?;
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
} else {
return Err(Errors::LineParseError {
line_number: line_count,
});
}
}
}
}
if line_count == 0 {
Err(Errors::LineParseError { line_number: 0 })
} else {
Ok(dungeon)
}
}
pub fn read_from_file(filename: &str) -> Result<Self, Errors> {
let file = File::open(filename).unwrap();
let reader = BufReader::new(file);
Dungeon::from_reader(reader)
}
}
/// match_prefix("- ", "- Foo") //=> Some("Foo")
/// match_prefix("- ", "Bar") //=> None
///
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
if input.starts_with(prefix) {
Some(&input[prefix.len()..])
} else {
None
}
}
pub struct Link {
from: String,
to: String,
direction: Direction,
}
fn match_link<'b>(input: &'b str) -> Option<Link> {
let mut link = Link {
from: String::new(),
to: String::new(),
direction: Direction::North,
};
let mut iter = input.split_terminator(" -> ");
link.from = iter.next().unwrap().to_string();
link.direction = match iter.next().unwrap() {
"North" => Direction::North,
"South" => Direction::South,
"East" => Direction::East,
"West" => Direction::West,
_ => return None,
};
link.to = iter.next().unwrap().to_string();
Some(link)
}
enum Parsing {
Room,
Links,
}
impl Dungeon {
/// Търси път от `start_room_name` до `end_room_name` и го връща във вектор, пакетиран във
/// `Ok(Some(` ако намери.
///
/// Ако няма път между тези две стаи, връща `Ok(None)`.
///
/// Ако четенето на стаи в един момент върне грешка, очакваме да върнете грешката нагоре.
///
pub fn find_path(
&self,
start_room_name: &str,
end_room_name: &str,
) -> Result<Option<Vec<&Room>>, Errors> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut parents = HashMap::new();
queue.push_front(start_room_name);
visited.insert(start_room_name);
parents.insert(start_room_name, None);
while let Some(room_name) = queue.pop_back() {
if room_name == end_room_name {
let mut path = Vec::new();
let mut current_room = room_name;
while let Some(parent) = parents.get(current_room) {
if !parent.is_none() {
path.push(self.rooms.get(current_room).unwrap().as_ref());
current_room = parent.unwrap();
} else {
break;
}
}
path.push(self.rooms.get(start_room_name).unwrap().as_ref());
path.reverse();
return Ok(Some(path));
}
for direction in Direction::iter() {
if let Ok(Some(neigh)) = self.get_next_room(room_name, direction) {
let neigh_name = neigh.name.as_str();
if !visited.contains(&neigh_name) {
queue.push_front(neigh_name);
visited.insert(neigh_name);
parents.insert(neigh_name, Some(room_name));
}
}
}
}
Ok(None)
}
}
#[test]
fn create_empty_dungeon() {
let dungeon = Dungeon::new();
assert_eq!(dungeon.rooms.len(), 0);
}
#[test]
fn add_room_to_empty_dungeon() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Room 1").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
dungeon.add_room("Room 2").unwrap();
assert_eq!(dungeon.rooms.len(), 2);
}
#[test]
fn duplicate_room() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Room 1").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
dungeon.add_room("Room 1").unwrap_err();
assert_eq!(dungeon.rooms.len(), 1);
}
#[test]
fn add_connection_and_get_room() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(
dungeon
.get_next_room("Hallway", Direction::West)
.unwrap()
.unwrap()
.name,
"Entrance"
);
}
#[test]
fn override_connection() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon.add_room("Magic Lab").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
dungeon
.set_link("Hallway", Direction::West, "Magic Lab")
.unwrap();
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
assert_eq!(
dungeon
.get_next_room("Hallway", Direction::West)
.unwrap()
.unwrap()
.name,
"Magic Lab"
);
}
#[test]
fn get_room_not_found() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_next_room("Entrance", Direction::West).unwrap(), None);
}
#[test]
fn concet_one_room_with_self() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Entrance").unwrap();
assert_eq!(dungeon.rooms.len(), 1);
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Entrance");
assert_eq!(dungeon.get_next_room("Entrance", Direction::West).unwrap().unwrap().name, "Entrance");
}
#[test]
-fn read_dungeon1_file() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon1.txt").unwrap();
-
- assert_eq!(dungeon.rooms.len(), 3);
- assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
- assert_eq!(dungeon.get_next_room("Hallway", Direction::West).unwrap().unwrap().name, "Magic Lab");
- assert_eq!(dungeon.get_next_room("Magic Lab", Direction::East).unwrap().unwrap().name, "Hallway");
-}
-
-#[test]
-fn read_dungeon2_file() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon2.txt").unwrap();
-
- assert_eq!(dungeon.rooms.len(), 6);
- assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
- assert_eq!(dungeon.get_next_room("Entrance", Direction::North).unwrap().unwrap().name, "Exit");
- assert_eq!(dungeon.get_next_room("Hallway", Direction::West).unwrap().unwrap().name, "Entrance");
- assert_eq!(dungeon.get_next_room("Hallway", Direction::North).unwrap().unwrap().name, "Bedroom");
- assert_eq!(dungeon.get_next_room("Hallway", Direction::South).unwrap().unwrap().name, "Magic Lab");
- assert_eq!(dungeon.get_next_room("Bedroom", Direction::West).unwrap().unwrap().name, "Exit");
- assert_eq!(dungeon.get_next_room("Bedroom", Direction::South).unwrap().unwrap().name, "Magic Lab");
- assert_eq!(dungeon.get_next_room("Magic Lab", Direction::North).unwrap().unwrap().name, "Bedroom");
- assert_eq!(dungeon.get_next_room("Exit", Direction::South).unwrap().unwrap().name, "Entrance");
- assert_eq!(dungeon.get_next_room("Exit", Direction::East).unwrap().unwrap().name, "Bedroom");
- assert_eq!(dungeon.get_next_room("Exit", Direction::North).unwrap().unwrap().name, "Special Room");
- assert_eq!(dungeon.get_next_room("Special Room", Direction::South).unwrap().unwrap().name, "Exit");
-}
-
-#[test]
-fn test_path_1() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon2.txt").unwrap();
- let path = dungeon.find_path("Entrance", "Magic Lab").unwrap().unwrap();
-
- assert_eq!(path.len(), 3);
- assert_eq!(path[0].name, "Entrance");
- assert_eq!(path[1].name, "Hallway");
- assert_eq!(path[2].name, "Magic Lab");
-}
-
-#[test]
fn test_path_cycle() {
let mut dungeon = Dungeon::new();
dungeon.add_room("A").unwrap();
dungeon.set_link("A", Direction::West, "A").unwrap();
let path = dungeon.find_path("A","A").unwrap().unwrap();
assert_eq!(path.len(),1);
assert_eq!(path[0].name,"A");
-}
-
-#[test]
-fn test_path_2() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon1.txt").unwrap();
- let path = dungeon.find_path("Entrance", "Magic Lab").unwrap().unwrap();
-
- assert_eq!(path.len(), 3);
- assert_eq!(path[0].name, "Entrance");
- assert_eq!(path[1].name, "Hallway");
- assert_eq!(path[2].name, "Magic Lab");
-}
-
-#[test]
-fn test_path_3() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon3.txt").unwrap();
- let path = dungeon.find_path("A", "Z").unwrap().unwrap();
-
- assert_eq!(path.len(), 4);
- assert_eq!(path[0].name, "A");
- assert_eq!(path[1].name, "C");
- assert_eq!(path[2].name, "D");
- assert_eq!(path[3].name, "Z");
-}
-
-#[test]
-fn test_path_4() {
- let dungeon = Dungeon::read_from_file("tests/test_dungeon4.txt").unwrap();
- let path = dungeon.find_path("A", "Z").unwrap();
-
- assert_eq!(path, None);
}
#[test]
#[should_panic]
fn test_read_error() {
Dungeon::read_from_file("tests/test_dungeon_error.txt").err().unwrap();
}
#[test]
fn test_basic_1() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon.set_link("Entrance", Direction::East, "Hallway").unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
}
const TEST_INPUT_1: &str = "
## Rooms
- Entrance
- Hallway
## Links
- Entrance -> East -> Hallway
";
#[test]
fn test_basic_2() {
// .trim() за да премахнем първия и последния ред:
let dungeon = Dungeon::from_reader(TEST_INPUT_1.trim().as_bytes()).unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(dungeon.get_next_room("Entrance", Direction::East).unwrap().unwrap().name, "Hallway");
}
#[test]
fn test_basic_3() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Treasure Room").unwrap();
dungeon.set_link("Entrance", Direction::West, "Treasure Room").unwrap();
let path = dungeon.find_path("Entrance", "Treasure Room").unwrap().unwrap();
assert!(path.len() > 0);
}