#text-adventure #rpg #game

app shards_of_aether

A text-based adventure RPG built in Rust

4 releases

0.1.3 Nov 12, 2025
0.1.2 Nov 12, 2025
0.1.1 Nov 12, 2025
0.1.0 Nov 12, 2025

#177 in Games

MIT license

51KB
694 lines

Shards of Aether

A text-based adventure RPG written in Rust, featuring exploration, turn-based combat, and dynamic world progression through multiple JSON-defined maps.

Game Overview

Shards of Aether is a command-line adventure game where the player explores interconnected levels, battles enemies, collects relics, and uncovers the fate of a fractured world.

Each area is defined in structured JSON files that load dynamically, allowing smooth transitions between levels while preserving the player’s progress and inventory. The gameplay emphasizes strategic combat choices, exploration, and resource management.

Core Features

  • JSON-Based World System
    Every level (map) is defined as a JSON file containing rooms, items, exits, and enemies. The engine dynamically loads and unloads these maps when transitioning between levels.
    Rooms have coordinates (x, y) that allow for future map rendering and procedural expansion.

  • Turn-Based Combat System
    Engage enemies using simple text commands: attack / heal / defend / run The system is turn-driven — enemies only attack after a valid player move, preventing damage from typos or invalid input.

Each victory grants XP, scaling with enemy strength, and lets the player progress further into the world.

  • Command Parsing & Input Handling
    The command system parses raw text input into structured enums like Command::Go, Command::Use, Command::Look, etc.
    This structure allows easily adding new commands or interactions (e.g. puzzles, special actions).

  • Player Progress & Inventory System
    The player’s data — health, inventory, XP, and current room — is stored persistently between levels.
    Items are typed (Healing, Weapon, Quest, Utility) to enable flexible in-game effects.

  • Level Transitions via JSON Loading
    When entering certain rooms (like “Sanctum”), the game seamlessly loads the next map file and repositions the player.
    Player inventory, health, and XP remain intact across transitions.

  • Dynamic Map Rendering
    The world map is generated by reading the x and y coordinates of each room, then printing a simplified ASCII minimap showing where the player currently is.
    This approach uses computed bounds (min_x, max_x, etc.) to scale automatically for any map layout.

Gameplay Example

You are standing in a grand hall with a glittering chandelier.
Exits: north → library, east → kitchen

> go north
You move north.
🧍 You have entered: river_bridge
river_bridge

A fragile wooden bridge spans the river. You hear rustling in the bushes.
Exits: west, south
⚠️ You sense danger nearby... (Goblin)

⚔️ A wild Goblin appears!
⚔️ You encounter a Goblin!
A sneaky little creature with a rusty dagger.

❤️ Your HP: 4 | 💀 Goblin’s HP: 20
Choose an action (attack / heal / defend / run):
> defend
🛡️ You brace yourself!
The Goblin attacks you for 4 damage!

If your HP reaches 0, the game displays a death ASCII art screen and exits dramatically:

💀 You have been defeated!

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⣶⡆⠀⣰⣿⠇⣾⡿⠛⠉⠁
⠀⣠⣴⠾⠿⠿⠀⢀⣾⣿⣆⣀⣸⣿⣷⣾⣿⡿⢸⣿⠟⢓⠀⠀
⣴⡟⠁⣀⣠⣤⠀⣼⣿⠾⣿⣻⣿⠃⠙⢫⣿⠃⣿⡿⠟⠛⠁⠀
⢿⣝⣻⣿⡿⠋⠾⠟⠁⠀⠹⠟⠛⠀⠀⠈⠉⠀⠉⠀⠀⠀⠀⠀
⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⣀⢀⣠⣤⣴⣤⣄⠀
⠀⠀⠀⠀⣀⣤⣤⢶⣤⠀⠀⢀⣴⢃⣿⠟⠋⢹⣿⣣⣴⡿⠋⠀
⠀⠀⣰⣾⠟⠉⣿⡜⣿⡆⣴⡿⠁⣼⡿⠛⢃⣾⡿⠋⢻⣇⠀⠀
⠀⠐⣿⡁⢀⣠⣿⡇⢹⣿⡿⠁⢠⣿⠷⠟⠻⠟⠀⠀⠈⠛⠀⠀
⠀⠀⠙⠻⠿⠟⠋⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

Game Over. Thanks for playing Adventurer!
Another shall be sent to complete what you have failed in.

Technical Overview

JSON Data Structure

Each level file (level1.json, sanctum.json, etc.) follows this structure:

{
  "rooms": {
    "hall": {
      "id": "hall",
      "description": "You are standing in a grand hall with a glittering chandelier.",
      "items": [
        {
          "name": "rusty key",
          "description": "An old, rusty key. I wonder what it opens."
        }
      ],
      "exits": { "north": "library", "east": "kitchen" },
      "x": 0,
      "y": 0
    }
  }
}

The engine deserializes these into Room structs using serde:

#[derive(Serialize, Deserialize, Clone)]
pub struct Room {
    pub id: String,
    pub description: String,
    #[serde(default)] pub items: Vec<Item>,
    #[serde(default)] pub exits: HashMap<String, String>,
    #[serde(default)] pub enemy: Option<String>,
    #[serde(default)] pub x: i32,
    #[serde(default)] pub y: i32,
}

This makes missing fields safe by using #[serde(default)], avoiding crashes from incomplete JSON.

Tech Stack

  • Language: Rust 🦀
  • Serialization: serde
  • Persistence: JSON map files
  • CLI Input: std::io utilities
  • Game State: Struct-based, with enums for Command, ItemType, and EnemyType

Running the Game

  1. Clone the project
git clone https://github.com/yourusername/shards_of_aether.git
cd shards_of_aether
  1. Build and run
cargo build
cargo run
  1. Explore! Use commands like:
go north
look
inventory
use potion
save
load
quit

License

This project is licensed under the MIT License.

Dependencies

~2–14MB
~116K SLoC