← All Workshops
MudEngine Part 3: File-Based World Loading
Step 5 / 8
Update the World model
Replace the hardcoded World::new() with a World::from_file(path) method that deserializes the TOML file.
We define serde-compatible structs for the TOML format — TomlRoom includes a numeric id field, and each exit's destination is a room id. The loader copies the data straight through; no name-to-index resolution is needed.
mud-engine-repl/src/main.rs
use std::io::{self, Write}; use serde::Deserialize; #[derive(Deserialize)] struct TomlRoom { id: usize, name: String, description: String, exits: Vec<TomlExit>, } #[derive(Deserialize)] struct TomlExit { direction: String, destination: usize, } #[derive(Deserialize)] struct TomlWorld { rooms: Vec<TomlRoom>, } struct Room { id: usize, name: String, description: String, exits: Vec<(String, usize)>, } struct World { rooms: Vec<Room>, player_room: usize, } impl World { fn from_file(path: &str) -> Result<Self, String> { let content = std::fs::read_to_string(path) .map_err(|e| format!("Failed to read {}: {}", path, e))?; let toml_world: TomlWorld = toml::from_str(&content) .map_err(|e| format!("Failed to parse {}: {}", path, e))?; let rooms: Vec<Room> = toml_world .rooms .iter() .map(|r| Room { id: r.id, name: r.name.clone(), description: r.description.clone(), exits: r.exits.iter().map(|e| (e.direction.clone(), e.destination)).collect(), }) .collect(); Ok(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 room lookup works
The loader no longer needs a name-to-index map. Since both the TOML destination and the player_room use numeric room ids, the look and go methods use iter().find() to locate rooms:
let room = self.rooms.iter().find(|r| r.id == self.player_room).unwrap();
This linear scan is fine for a few rooms. For thousands of rooms, you would build a HashMap<usize, &Room> for O(1) lookups — but that is an optimisation you can add later when you need it.
Step 5 / 8