MudEngine Part 6: Multiplayer
Enhanced WorldGrid
WorldGrid is rewritten to read from reactive multiplayer signals instead of the single-player Signal<World>. It takes two ReadSignal props plus an onmove callback:
players— aHashMap<String, PlayerInfo>keyed by player IDmy_id— the current player's ID, used to determine which room is "active"onmove: Callback<String>— fires with a direction name whenever the player moves (via D-pad, a room click, or the keyboard)
The grid is built by mapping over the static ROOM_NAMES array. For each room, WorldGrid:
- Looks up the current player's room via
my_idto mark the active cell - Uses the shared
EXITStable to compute which rooms are reachable from the current room — those cells get theclickableprop, so they are highlighted and can be clicked to move - Collects the names of every player whose
roomindex matches, excluding the current player (so the indicator shows others, not yourself) - Creates a
RoomCellwith those occupants, wired up so clicking a reachable cell callsonmovewith that room's direction
Replace the WorldGrid in src/components/world_grid.rs with this multiplayer-aware version. It now imports the room data and message types from the game module, plus the RoomCell component:
use std::collections::HashMap; use crate::components::room_cell::RoomCell; use crate::game::messages::PlayerInfo; use crate::game::room_data::{EXITS, ROOM_NAMES}; use dioxus::prelude::*; #[component] pub fn WorldGrid( players: ReadSignal<HashMap<String, PlayerInfo>>, my_id: ReadSignal<String>, onmove: Callback<String>, ) -> Element { let my_room = players.read().get(&*my_id.read()).map(|p| p.room); // Rooms reachable from the current room, mapped to the direction name let reachable: HashMap<usize, &str> = match my_room { Some(room) => EXITS .get(room) .map(|exits| { exits.iter().map(|&(dir, target)| (target, dir)).collect() }) .unwrap_or_default(), None => HashMap::new(), }; let cells: Vec<Element> = ROOM_NAMES.iter().enumerate().map(|(idx, name)| { let occupants: Vec<String> = players.read().values() .filter(|p| p.room == idx) .filter(|p| *my_id.read() != p.id) .map(|p| p.name.clone()) .collect(); let clickable = reachable.contains_key(&idx); let dir = reachable.get(&idx).copied().unwrap_or("").to_string(); rsx! { RoomCell { key: "{idx}", name: name.clone(), active: my_room == Some(idx), clickable, players: occupants, onclick: move |_| if !dir.is_empty() { onmove.call(dir.clone()) }, } } }).collect(); rsx! { div { class: "world-grid", {cells.into_iter()} } } }
EXITS maps each room index to a list of (direction, destination) pairs. WorldGrid inverts that table for the current room: it builds a HashMap<usize, &str> where the key is a destination room index and the value is the direction string leading there.
For Town Square (room 4) the shared EXITS is:
&[("north", 1), ("south", 7), ("west", 3), ("east", 5)]
So reachable becomes {1: "north", 7: "south", 3: "west", 5: "east"}. Cells 1, 7, 3, and 5 are marked clickable and send "north", "south", "west", "east" respectively when clicked. Every other cell is inert — same behaviour as the Part 5 room cards, but now the move goes through the server.
If the player somehow has no room yet (my_room is None), reachable is empty and every cell is inert until State arrives.