← All Workshops

MudEngine Part 4: Single-Player Dioxus GUI

Step 5 / 10

The World model — expanded to 3×3

We reuse the same Room and World structs from Part 2, but expand the map to 9 rooms arranged in a 3×3 grid.

The player starts in room 4 — Town Square, the center of the grid. Each room connects to its grid neighbours via cardinal exits (north, south, east, west).

The two methods we need are:

  • look() — formats the current room's name, description, and exit list into a String
  • go(dir) — looks for a matching exit; if found, updates player_room and returns true; otherwise returns false

Add this code to your src/main.rs, replacing the auto-generated content.

🗺️ 3×3 Grid
RowCol 0Col 1Col 2
0Forest Path (0)Hilltop (1)Abandoned Tower (2)
1Dark Forest (3)Town Square (4)Temple Courtyard (5)
2Riverbank (6)Old Bridge (7)Graveyard (8)
mud-engine/src/main.rs
use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[derive(Clone, PartialEq)]
struct Room {
    name: String,
    description: String,
    exits: Vec<(String, usize)>,
}

#[derive(Clone, PartialEq)]
struct World {
    rooms: Vec<Room>,
    player_room: usize,
}

impl World {
    fn new() -> Self {
        let rooms = vec![
            Room {
                name: "Forest Path".into(),
                description: "A winding path leads through ancient oaks. Sunlight \
                              filters through the canopy.".into(),
                exits: vec![("south".into(), 3), ("east".into(), 1)],
            },
            Room {
                name: "Hilltop".into(),
                description: "From this vantage point you can see the entire valley. \
                              A cool breeze carries the scent of pine.".into(),
                exits: vec![("south".into(), 4), ("west".into(), 0), ("east".into(), 2)],
            },
            Room {
                name: "Abandoned Tower".into(),
                description: "A crumbling stone tower stands alone. Vines crawl up \
                              its walls and crows nest in the windows.".into(),
                exits: vec![("south".into(), 5), ("west".into(), 1)],
            },
            Room {
                name: "Dark Forest".into(),
                description: "Twisted trees block out most of the light. Strange \
                              sounds echo through the undergrowth.".into(),
                exits: vec![("north".into(), 0), ("south".into(), 6), ("east".into(), 4)],
            },
            Room {
                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(), 1),
                    ("south".into(), 7),
                    ("west".into(), 3),
                    ("east".into(), 5),
                ],
            },
            Room {
                name: "Temple Courtyard".into(),
                description: "Ancient stone pillars surround a quiet courtyard. Moss \
                              clings to weathered statues.".into(),
                exits: vec![("north".into(), 2), ("south".into(), 8), ("west".into(), 4)],
            },
            Room {
                name: "Riverbank".into(),
                description: "A slow-moving river borders a muddy bank. Frogs croak \
                              from the reeds.".into(),
                exits: vec![("north".into(), 3), ("east".into(), 7)],
            },
            Room {
                name: "Old Bridge".into(),
                description: "A weathered stone bridge crosses the river. Moss covers \
                              the ancient masonry.".into(),
                exits: vec![("north".into(), 4), ("west".into(), 6), ("east".into(), 8)],
            },
            Room {
                name: "Graveyard".into(),
                description: "Rows of moss-covered headstones stretch into the fog. \
                              An iron gate creaks in the wind.".into(),
                exits: vec![("north".into(), 5), ("west".into(), 7)],
            },
        ];
        World {
            rooms,
            player_room: 4,
        }
    }

    fn look(&self) -> String {
        let room = &self.rooms[self.player_room];
        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(' ');
            out.push_str(dir);
        }
        out
    }

    fn go(&mut self, direction: &str) -> bool {
        for (dir, idx) in self.rooms[self.player_room].exits.clone() {
            if dir == direction {
                self.player_room = idx;
                return true;
            }
        }
        false
    }
}
🔍 Why Clone + PartialEq?

In Dioxus 0.7, props must implement Clone and PartialEq. Dioxus uses PartialEq to detect whether a prop changed — if it did not change, the component skips re-rendering. Both derives are added automatically via #[derive(Clone, PartialEq)].

The PartialEq comparison on World walks every room, name, description, and exit. That is fine for 9 rooms but you would want a smarter scheme (like a version counter) for a larger world.

🔢 Grid indexing

Room index = row * 3 + col. The player starts in the center (4).

Exits mirror the grid: each room connects to its immediate neighbours. The go method checks whether an exit exists for the given direction.

Step 5 / 10