Bug in a text-based dungeon crawler

:slight_smile: o I recently started making a text-based game, and I was working on the room system, when I found out a bug. When I try to move to any other room than the two first I get this:

thread 'main' panicked at src/room/tuto_dungeon.rs:39:30:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I tried looking at the backtrace but I still couldn't find any clue of why the program crashes.
Here are the concerned files (yes I know it's bad written, I plan to refactor this later on):

src/main.rs

use std::io;

pub mod actions;

use crate::actions::*;

pub mod room;

pub mod game_data;
use game_data::Game;


fn main() {
    
    let mut game = Game {
        player_location: "room1",
    };

    println!("My dungeon crawler");

    let mut has_seen_description = false;
    
    loop {
        if has_seen_description == false {
            let _ = match_action("look", &mut game);
            has_seen_description = true;
        }

        let mut input = String::new();
        println!("What do you want to do?");

        io::stdin()
            .read_line(&mut input)
            .expect("Failed to read line");

        let result = match_action(&input, &mut game);
        match result {
            Ok(new_data) => {
                game = new_data;
            },
            Err(()) => {},
        }
    }

}

src/actions/mod.rs

use crate::room::{tuto_dungeon::test_dungeon, Exits};
use crate::room::*;
use crate::game_data::Game;

pub fn match_action(guess: &str, data: &mut Game) -> Result<Game, ()> {
    let first_word = guess.trim().split_whitespace().next().unwrap_or("");
    let second_word = guess.trim().split_whitespace().nth(1).unwrap_or("");
    
    match first_word {
        "look" => Err(look(data.player_location)),
        "l" => Err(look(data.player_location)),
        "north" => Ok(north(data.player_location)),
        "n" => Ok(north(data.player_location)),
        "south" => Ok(south(data.player_location)),
        "s" => Ok(south(data.player_location)),
        "east" => Ok(east(data.player_location)),
        "e" => Ok(east(data.player_location)),
        "west" => Ok(west(data.player_location)),
        "w" => Ok(west(data.player_location)),
        "read" => Err(read(second_word, data.player_location)),
        "quit" => Err(quit()),
        "q" => Err(quit()),
        _ => Err(unknown()),
    }
}

#[allow(unsafe_code)]
pub fn look(location: &str) -> () {
    let current_room = test_dungeon(location);

    println!("{}", current_room.description);
    println!("Exits: {:?}", current_room.exits.keys());
}

pub fn north(mut location: &'static str) -> Game {
    let current_room = test_dungeon(location);

    match current_room.exits.get(&Exits::North) {
        Some(room) => {
            location = room;
            look(location);
            Game {
                player_location: location,
            }
    },
        None => { println!("You can't go north."); Game{ player_location: location } },
    }
}

pub fn south(mut location: &'static str) -> Game {
    let current_room = test_dungeon(location);

    match current_room.exits.get(&Exits::South) {
        Some(room) => {
            location = room;
            look(location);
            Game {
                player_location: location,
            }
    },
        None => { println!("You can't go south."); Game{ player_location: location } },
    }
}

pub fn east(mut location: &'static str) -> Game {
    let current_room = test_dungeon(location);

    match current_room.exits.get(&Exits::East) {
        Some(room) => {
            location = room;
            look(location);
            Game {
                player_location: location,
            }
    },
        None => { println!("You can't go east."); Game{ player_location: location } },
    }
}

pub fn west(mut location: &'static str) -> Game {
    let current_room = test_dungeon(location);

    
    match current_room.exits.get(&Exits::West) {
        Some(room) => {
            location = room;
            look(location);
            Game {
                player_location: location,
            }
    },
        None => { println!("You can't go west."); Game{ player_location: location } },
    }
}

pub fn read (to_read: &str, location: &str) -> () {
    let room = test_dungeon(location);

    for i in room.items {
        match i {
            Items::Sign(_vec, desc) => {
                if to_read == "sign" {
                    println!("{}", desc);
                }
                else {
                    println!("You can't read that.");
                }
            },
            _ => {},
        }
    }
}

pub fn quit() -> () {
    println!("Bye bye!");
    std::process::exit(0);
}

pub fn unknown() -> () {
    println!("You can't do that.");
}

pub fn match_item(item: &str) -> Items {
    match item {
        "sign" => Items::Sign(vec![], ""),
        "key" => Items::Key,
        "door" => Items::Door,
        "potion" => Items::Potion,
        "dagger" => Items::Dagger,
        _ => Items::None,
    }
}

src/room/mod.rs

pub mod tuto_dungeon;
use std::collections::HashMap;

#[derive(Clone, Debug)]
#[derive(Eq, Hash, PartialEq)]
pub enum Exits {
    North,
    South,
    East,
    West,
    Up,
    Down,
}

#[derive(Clone, Debug)]
#[derive(Eq, Hash, PartialEq)]
pub enum Items {
    Sign(Vec<&'static str>, &'static str),
    Key,
    Door,
    Potion,
    Dagger,
    None,
}

#[derive(Clone, Debug)]
pub struct Room {
    pub description: &'static str,
    pub exits: HashMap<Exits, &'static str>,
    pub items: Vec<Items>,
}

pub fn match_item(item: &str) -> Items {
    match item {
        "sign" => Items::Sign(vec![], ""),
        "key" => Items::Key,
        "door" => Items::Door,
        "potion" => Items::Potion,
        "dagger" => Items::Dagger,
        _ => Items::None,
    }
}

src/room/tuto_dungeon.rs

use crate::room::*;
use std::collections::HashMap;

pub fn test_dungeon(id: &str) -> Room {
    let mut room1 = Room {
        description: "You're in a dark room. There is a sign hanging on the wall.",
        exits: HashMap::new(),
        items: vec![Items::Sign(vec!["sign"], "Welcome to my dungeon!")],
    };
    room1.exits.insert(Exits::East, "room2");
    room1.exits.insert(Exits::West, "room4");

    let mut room2 = Room {
        description: "This is room number 2",
        exits: HashMap::new(),
        items: vec![Items::Sign(vec!["sign"], "This is the second sign.")],
    };
    room2.exits.insert(Exits::West, "room1");
    room2.exits.insert(Exits::North, "room3");

    let mut room3 = Room {
        description: "This is room number 3",
        exits: HashMap::new(),
        items: vec![Items::Sign(vec!["sign"], "This is the third sign.")],
    };
    room3.exits.insert(Exits::South, "room2");

    let mut room4 = Room {
        description: "This is room number 4",
        exits: HashMap::new(),
        items: vec![Items::Sign(vec!["sign"], "This is the fourth sign.")],
    };
    room4.exits.insert(Exits::East, "room1");

    let mut dungeon = HashMap::new();
    dungeon.insert("room1", room1);
    dungeon.insert("room2", room2);

    dungeon.get(id).cloned().unwrap()
}

Here is the backtrace:

0: rust_begin_unwind
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
   1: core::panicking::panic_fmt
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
   2: core::panicking::panic
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:144:5
   3: core::option::Option<T>::unwrap
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/option.rs:931:21
   4: dungeon::room::tuto_dungeon::test_dungeon
             at ./src/room/tuto_dungeon.rs:39:5
   5: dungeon::actions::look
             at ./src/actions/mod.rs:29:24
   6: dungeon::actions::north
             at ./src/actions/mod.rs:41:13
   7: dungeon::actions::match_action
             at ./src/actions/mod.rs:13:19
   8: dungeon::main
             at ./src/main.rs:36:22
   9: core::ops::function::FnOnce::call_once
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5

Do you have any idea of what is happening?

If you ignore the backtrace, note that the error message explains the problem:

thread 'main' panicked at src/room/tuto_dungeon.rs:39:30:
called `Option::unwrap()` on a `None` value

From the unwrap docs:

Panics if the self value equals None.

So, calling unwrap on a None value panics and the program crashes here:

    let mut dungeon = HashMap::new();
    dungeon.insert("room1", room1);
    dungeon.insert("room2", room2);

    dungeon.get(id).cloned().unwrap()

I think one fix is to add the missing rooms to your dungeon:

    dungeon.insert("room1", room1);
    dungeon.insert("room2", room2);
+   dungeon.insert("room3", room3);
+   dungeon.insert("room4", room4);
1 Like

Oh okay, I just forgot to put it in the dungeon hash map. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.