
Table of Contents
🧟 Section 1: Introduction – From Exploration to Confrontation
Rust MUD Game was already fun in the last lesson — we built a simple world made of rooms.
You could walk around using commands like "go north"
or "go east"
.
Each room had a name, a little story, and paths leading to other rooms.
If you haven’t read Part 1 yet, check it out to understand how the core map system was built — this article builds directly on that foundation.
That was cool. But… something was missing.
What is a world without excitement? Without danger? Without adventure?
So now, in Part 2, we will add something exciting: monsters.
Imagine you’re in a dark forest, and suddenly… a goblin jumps out!
You can’t just walk around anymore. You must decide: run, fight, or maybe talk?
This part will teach you:
- How to create monsters in your code
- How to put monsters inside rooms
- How to attack monsters using commands
- And how to make rooms feel more alive with monster surprises
🧠 Why is this important?
Because it teaches your program to:
- Make decisions
- Remember what’s inside each room
- React when the player does something
That’s called interaction.
Instead of just moving around, now the game answers back when you do something.
Are you ready to make your world come alive?
Let’s go step by step.
🧪 Section 2: Designing the Monster System
🧠 What is a Monster in a Game?
In a game, a monster is not scary lines of code. It’s just a collection of information.
Let’s think of a monster like a character in a story. It has:
- A name (What kind of monster is it?)
- Some health (How strong is it? How long can it fight?)
- A damage value (How much hurt can it do to you?)
🧱 Step 1: Let’s Describe the Monster
In Rust, we use a struct to group related information together.
It’s like a box where we can store all the monster’s details in one place.
Here’s what the code looks like:
#[derive(Clone)]
struct Monster {
name: String,
health: i32,
damage: i32,
}
Let’s explain it line by line:
#[derive(Clone)]
– This means we can copy the monster if we want to use the same kind in many rooms.struct Monster { ... }
– This makes a blueprint calledMonster
.- Inside the
{}
, we write what the monster knows about itself:name
: a word like"Goblin"
or"Dragon"
health
: a number like 30 or 100 (how much life it has)damage
: a number like 5 or 10 (how much it hurts you when it attacks)
📦 Example Monster in Words
“This is a Goblin. It has 30 health points. When it attacks, it deals 5 damage.”
That sentence becomes:
Monster {
name: "Goblin".to_string(),
health: 30,
damage: 5,
}
Easy, right?
🏠 Step 2: Let’s Put a Monster Inside a Room
We want to add monsters to our map.
So now we’ll update our Room
struct from Part 1.
We add a new line to the room:
struct Room {
name: String,
description: String,
exits: HashMap<String, String>,
monster: Option<Monster>,
}
Let’s break it down:
monster: Option<Monster>
– This means:- The room might have a monster.
- Or it might not. It’s optional.
- Why
Option
? Because not every room is dangerous. Some are safe!
This way, the game can check:
“Is there a monster here?” If yes → show it. If no → move on.
🧠 Algorithm Thinking
What are we doing logically?
FOR each room IN the game:
IF we want it to be dangerous:
PUT a monster in it
ELSE:
Leave it empty
This is how we teach the computer to think like a game designer.

🗺️ Section 3: Placing Monsters on the Map
📌 What does “placing” a monster mean?
It means:
“I want to put a Goblin in the Dark Forest room.”
In programming, we don’t draw the goblin — we tell the computer that:
- This room has a monster,
- And this monster has a name, health, and damage.
Let’s walk through how to do that using Rust code!
🧱 Step 1: Prepare Your Room Map
In Part 1, we made a HashMap that holds all the rooms.
Think of it like a big notebook:
- The key (title) is the room name:
"forest"
- The value (content) is the
Room
with its details
Let’s add a new room called "forest"
that includes a monster.
rooms.insert("forest".to_string(), Room {
name: "Dark Forest".to_string(),
description: "A shadowy forest with rustling leaves.".to_string(),
exits: hashmap! {
"south".to_string() => "village".to_string()
},
monster: Some(Monster {
name: "Goblin".to_string(),
health: 30,
damage: 5,
}),
});
Let’s break that down:
"forest"
is the ID for the room"Dark Forest"
is the name that the player sees"south"
→"village"
means there’s a way to go southmonster: Some(Monster { ... })
means:- “Yes, there is a monster here!”
- And here’s what kind: Goblin, 30 health, 5 damage
💬 How does Some()
work?
In Rust, Some(monster)
means the room has a monster.
If we used None
, it would mean no monster.
So now our logic looks like this:
If room.monster is Some(Monster):
Show the monster
Allow fight
Else:
Room is safe
🧠 Algorithm Summary (in plain English)
Here’s what your game now knows how to do:
1. Player enters a room.
2. Game checks: Is there a monster?
3. If yes:
- Show the monster’s name
- Save its health and damage
4. If no:
- Say “This room is peaceful.”
You’ve just made your map come alive!
Now rooms are not just empty boxes — some are dangerous, and some are safe zones.
🗡️ Section 4: Implementing the attack
Command
🎮 What is a command?
In our game, the player types commands like:
"go north"
"look"
- And now…
"attack"
Each command tells the computer: do something.
So "attack"
means:
“If there’s a monster here, I want to hit it!”
Let’s learn how to teach our game what to do when someone types "attack"
.
🧱 Step 1: Add the Command to the Game Loop
In Rust, you probably have something like this in your code:
match command.as_str() {
"go" => { ... }
"look" => { ... }
_ => println!("Unknown command."),
}
Now, let’s add "attack"
to this match
block.
⚔️ Step 2: Write the Attack Logic
"attack" => {
if let Some(room) = rooms.get_mut(current_room) {
if let Some(monster) = &mut room.monster {
println!("You attack the {}!", monster.name);
monster.health -= 10;
if monster.health <= 0 {
println!("You defeated the {}!", monster.name);
room.monster = None;
} else {
println!("The {} hits you back for {} damage!", monster.name, monster.damage);
// (Optional) You could add player health here!
}
} else {
println!("There is nothing to attack here.");
}
}
}
Let’s break this down:
🧠 Line-by-Line Explanation
Line | What It Means |
---|---|
if let Some(room) = rooms.get_mut(current_room) | Look up the room you’re in |
if let Some(monster) = &mut room.monster | Check if a monster is in the room |
println!("You attack the {}!", monster.name); | Show the action to the player |
monster.health -= 10; | Reduce the monster’s health by 10 |
if monster.health <= 0 | Is the monster dead? |
room.monster = None; | Remove the monster from the room |
else { ... } | If still alive, the monster attacks back! |
📐 Algorithm in Plain English
1. Player types “attack”
2. Game checks the current room
3. If there’s a monster:
- Player hits monster (reduce health)
- If health is 0 or less → monster dies
- Else → monster hits back!
4. If no monster → show a message
🚀 Try It Out!
Now when you play the game:
- Walk into the
"forest"
room - Type
"attack"
multiple times - See the monster’s health go down
- Watch it disappear when defeated
🎉 Boom! You just built your first battle system!
println!(“It has {} health and does {} damage.”, monster.health, monster.damage);
🌟 Why show monsters automatically?
Right now, the only way the player knows a monster is there is if they type "attack"
.
But that’s not very fun, right?
What if the game could say:
“A wild Goblin appears!”
…as soon as you enter the room?
That way, the room feels alive, and the player knows there’s a choice to make:
fight, run, or prepare.
🧱 Step 1: Update the Room Entry Code
Whenever a player enters a room (like after typing "go north"
), you probably already show them the room’s name and description:
println!("You are in the {}.", room.name);
println!("{}", room.description);
Now we add this check right after:
if let Some(monster) = &room.monster {
println!("⚠️ A wild {} appears!", monster.name);
}
💬 What this does:
- If there’s a monster, we print a message with its name
- If no monster? Nothing happens, and the room is peaceful
🎯 Where exactly do I put this?
In your "go"
command (or room display function), just after loading the new room:
if let Some(room) = rooms.get(current_room) {
println!("You are in the {}.", room.name);
println!("{}", room.description);
// 👇 Monster alert!
if let Some(monster) = &room.monster {
println!("⚠️ A wild {} appears!", monster.name);
}
}
This makes the game feel more responsive and exciting.
🧠 Algorithm in Simple Words
1. Player moves into a room
2. Show the room’s name and description
3. Check if a monster is inside
4. If yes → show a message like “A monster is here!”
💡 Bonus Tip: Add Monster Health Info
You can also add this for extra clarity:
println!("It has {} health and does {} damage.", monster.health, monster.damage);
So the player knows what they’re facing!
🧩 Section 6: Full Code Summary – What We’ve Built
Let’s take a deep breath and look at what we’ve created in Part 2:
✅ You learned how to:
- Create a
Monster
struct with name, health, and damage - Add a monster to any room using
Option<Monster>
- Check if a monster is present when entering a room
- Let the player type
"attack"
to fight the monster - Remove the monster when its health reaches 0
- Show monster info automatically when entering a room
📄 Full Integrated Code: Movement + Monster System
use std::collections::HashMap;
use std::io;
#[derive(Clone)]
struct Monster {
name: &'static str,
health: i32,
damage: i32,
}
struct Room {
name: &'static str,
description: &'static str,
north: Option<&'static str>,
south: Option<&'static str>,
east: Option<&'static str>,
west: Option<&'static str>,
monster: Option<Monster>,
}
fn main() {
let mut rooms = HashMap::new();
rooms.insert("Mountains", Room {
name: "Mountains",
description: "You are high in the rocky mountains.",
north: None,
south: Some("Forest"),
east: None,
west: None,
monster: None,
});
rooms.insert("Forest", Room {
name: "Forest",
description: "You are standing in a dense, dark forest.",
north: Some("Mountains"),
south: Some("Abandoned Village"),
east: Some("Cave"),
west: None,
monster: Some(Monster {
name: "Goblin",
health: 30,
damage: 5,
}),
});
rooms.insert("Cave", Room {
name: "Cave",
description: "You are inside a damp cave.",
north: None,
south: None,
east: Some("Lake"),
west: Some("Forest"),
monster: None,
});
rooms.insert("Lake", Room {
name: "Lake",
description: "You stand by a clear, blue lake.",
north: None,
south: None,
east: None,
west: Some("Cave"),
monster: None,
});
rooms.insert("Abandoned Village", Room {
name: "Abandoned Village",
description: "You are in an abandoned, silent village.",
north: Some("Forest"),
south: Some("Old Temple"),
east: None,
west: None,
monster: None,
});
rooms.insert("Old Temple", Room {
name: "Old Temple",
description: "You are in the ruins of an ancient temple.",
north: Some("Abandoned Village"),
south: None,
east: Some("Desert"),
west: None,
monster: None,
});
rooms.insert("Desert", Room {
name: "Desert",
description: "You wander a vast, hot desert.",
north: None,
south: None,
east: None,
west: Some("Old Temple"),
monster: None,
});
let mut current_location = "Forest";
println!("🏕️ Welcome to the Rust MUD Game!");
println!("Type 'north', 'south', 'east', 'west' to move, 'attack' to fight, or 'quit' to exit.");
loop {
let room = rooms.get(current_location).unwrap();
println!("\n📍 Location: {}", room.name);
println!("{}", room.description);
if let Some(monster) = &room.monster {
println!("⚠️ A wild {} appears!", monster.name);
println!("It has {} health and deals {} damage.", monster.health, monster.damage);
}
println!("\nWhat do you want to do?");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read input");
match input.trim() {
"north" => {
if let Some(next_room) = room.north {
current_location = next_room;
} else {
println!("🚫 You can't go north from here.");
}
}
"south" => {
if let Some(next_room) = room.south {
current_location = next_room;
} else {
println!("🚫 You can't go south from here.");
}
}
"east" => {
if let Some(next_room) = room.east {
current_location = next_room;
} else {
println!("🚫 You can't go east from here.");
}
}
"west" => {
if let Some(next_room) = room.west {
current_location = next_room;
} else {
println!("🚫 You can't go west from here.");
}
}
"attack" => {
let room = rooms.get_mut(current_location).unwrap();
if let Some(monster) = &mut room.monster {
println!("🗡️ You attack the {}!", monster.name);
monster.health -= 10;
if monster.health <= 0 {
println!("🎉 You defeated the {}!", monster.name);
room.monster = None;
} else {
println!("💢 The {} hits you back for {} damage!", monster.name, monster.damage);
}
} else {
println!("There's nothing to attack here.");
}
}
"quit" => {
println!("👋 Thanks for playing! Goodbye!");
break;
}
_ => {
println!("❓ Invalid command. Use 'north', 'south', 'east', 'west', 'attack', or 'quit'.");
}
}
}
}
🧠 What did you really build?
You built a working mini-RPG engine:
- Each room can contain events (monsters)
- The game can react to what the player does
- And it can change based on what happens (monster disappears!)
That’s real game logic!
🔮 Section 7: What’s Next?
Great adventurer, you’ve just taken your first step into building a living world.
But we’re just getting started.
In Part 3, we’ll explore:
- 🎒 Inventory system – let players pick up items
- 🧃 Healing potions – recover HP after battle
- 💬 Friendly NPCs – talk instead of fight
- 🧠 Smarter monsters – maybe they won’t always attack!
🗣️ Final Message
Stay tuned for Part 3 of the Rust MUD Game Tutorial,
where your world evolves with real combat, monster encounters, and saving your progress.
This is just the beginning.
Your journey as a Rust game developer continues—with more power, more systems, and more adventure ahead.
🔗 External Reference Links for Rust MUD Game Development
- Riskpeep’s Rust Text Adventure Tutorial Series
A detailed guide on building a text adventure game from scratch in Rust, covering maps, game loops, and player input.
🔗 How to make a Text Adventure game in Rust – Part 1 - MuOxi – A Modern MUD Engine in Rust
A MUD engine framework built on Rust using Tokio and Diesel. Great for building multiplayer text-based games.
🔗 MuOxi GitHub Repository - DemiMUD – Lightweight Rust MUD Project
A learning project showcasing how to build a basic MUD in Rust, featuring entity management and dynamic command routing.
🔗 DemiMUD GitHub Repository - Kingslayer – A Playable Rust Text RPG
A Rust-powered text RPG you can play in the browser, with tools to define and build your own world.
🔗 Kingslayer GitHub Repository - Hack Club – Rust Text Game Workshop
Beginner-friendly workshop from Hack Club that walks through building a Rust-based text game step by step.
🔗 Text Adventure Game in Rust – Hack Club