← All Workshops

MudEngine Part 2: Single-Player REPL

Step 7 / 10

The World model

Every MUD centers on a World — the in-memory representation of all rooms, their connections, and the player's current position.

We model three things:

  1. Room — a location with a name, description, and list of exits
  2. World — owns all rooms and tracks the player's position
  3. Methods on World — look() describes the current room, go(dir) moves the player and returns the new room description
mud-engine-repl/src/main.rs
use std::io::{self, Write};

struct Room {
    id: usize,
    name: String,
    description: String,
    exits: Vec<(String, usize)>,
}

struct World {
    rooms: Vec<Room>,
    player_room: usize,
}

impl World {
    fn new() -> Self {
        let rooms = vec![
            Room {
                id: 1,
                name: "Town Square".into(),
                description: "A bustling town square with a fountain at its center. Cobblestones gleam from the morning rain.".
                    into(),
                exits: vec![("north".into(), 2), ("east".into(), 3)],
            },
            Room {
                id: 2,
                name: "Market Street".into(),
                description: "A narrow lane lined with wooden stalls. The scent of fresh bread and spices hangs in the air.".
                    into(),
                exits: vec![("south".into(), 1)],
            },
            Room {
                id: 3,
                name: "Temple Courtyard".into(),
                description: "Ancient stone pillars surround a quiet courtyard. Moss clings to weathered statues.".
                    into(),
                exits: vec![("west".into(), 1)],
            },
        ];
        World { rooms, player_room: 1 }
    }

    fn look(&self) -> String {
        let room = self.rooms.iter().find(|r| r.id == self.player_room).unwrap();
        let mut out = String::new();
        out.push_str(&room.name);
        out.push_str("\n");
        out.push_str(&room.description);
        out.push_str("\n\nExits:");
        for (dir, _) in &room.exits {
            out.push_str(" ");
            out.push_str(dir);
        }
        out
    }

    fn go(&mut self, direction: &str) -> String {
        let room = self.rooms.iter().find(|r| r.id == self.player_room).unwrap();
        for (dir, id) in &room.exits {
            if dir == direction {
                self.player_room = *id;
                return self.look();
            }
        }
        format!("You cannot go {} from here.", direction)
    }
}
🔍 How exits work

Each exit is a (String, usize) tuple — the direction as a word ("north", "south", etc.) and the numeric id of the destination room. Rooms have explicit id fields (1, 2, 3) that never change, even if rooms are reordered.

The go method uses iter().find() to locate the current room by its id, then searches its exits for a matching direction. If found, it updates player_room to the destination id and describes the new location. If not, it returns an error message — every MUD needs to tell the player when they cannot go that way.

Step 7 / 10