Creating New Environments

Overview

Creating new environments using MiniHack is very simple. There are two main MiniHack base classes to chose from.

MiniHackNavigation ba can be used to design mazes and navigation tasks that only require a small action space. All MiniHack navigation tasks make use of the MiniHackNavigation interface. The in-game multiple-choice question prompts, which are used for interacting with objects and using the inventory, are turned off by default here.

MiniHackSkill provides a convenient mean for designing diverse skill acquisition tasks that require a large action space, interactions with objects and more complex goals. All skill acquisition tasks in MiniHack use this base class. The in-game multiple-choice question prompts is turned on by default.

The quickest way for creating a new environment is to use gym.make and pass the description file to the environment:

import gym
import minihack

des_file = """
MAZE: "mylevel",' '
GEOMETRY:center,center
MAP
-------------
|.....|.....|
|.....|.....|
|.....+.....|
|.....|.....|
|.....|.....|
-------------
ENDMAP
REGION:(0,0,12,6),lit,"ordinary"
BRANCH:(1,1,6,6),(0,0,0,0)
DOOR:locked,(6,3)
STAIR:(8,3),down
"""
env = gym.make(
    "MiniHack-Navigation-Custom-v0",
    des_file=des_file,
    max_episode_steps=50,
)

Additional parameters for the environment can also be passed to gym.make, such as observation_keys, etc. By default, the goal of the agent is to reach the stair down. However, reward functions in MiniHack can easily be configured. See here for more information.

Alternatively, the users can subclass either MiniHackNavigation or MiniHackSkill classes.

from minihack import MiniHackNavigation
from minihack.envs import register

class MiniHackNewTask(MiniHackNavigation):
    def __init__(self, *args, des_file, **kwargs):
        kwargs["max_episode_steps"] = kwargs.pop("max_episode_steps", 1000)
        super().__init__(*args, des_file=des_file, **kwargs)

register(
    id="MiniHack-NewTask-v0",
    entry_point="path.to.file:MiniHackNewTask", # use the actual the path
)

For information about the description files, check out our brief overview, detailed tutorial or community wiki.

Level Generator

When creating a new MiniHack environment, a description file must be provided. One way of providing this des-file is writing it entirely from scratch. However, this requires learning the des-file format and is more difficult to do programmatically, so as part of MiniHack we provide the LevelGenerator class which provides a convenient wrapper around writing a des-file. The LevelGenerator class can be used to create MAZE-type levels with specified heights and widths, and can then fill those levels with objects, monsters and terrain, and specify the start point of the level. Combined with the RewardManager which handles rewards, this enables flexible creation of a wide variety of environments.

The level generator can start with either an empty maze (in which case only height and width are specified, see Example 1) or with a pre-drawn map (see Example 2). After initialisation, the level generator can be used to add objects, traps, monsters and other terrain features. Terrain can also be added (\cref{code:python} line 9). Once the level is complete, the get_des() function returns the des-file which can then be passed to the environment creation.

Example 1 shows how to create a simple skill acquisition task that challenges the agent to eat an apple and wield a dagger that is randomly placed in a 10x10 room surrounded by lava, alongside a goblin and a teleportation trap. Here, a RewardManager is used to specify the tasks that need to be completed.

Example 2 illustrates how to create a labyrinth task. Here, the agent starts near the entrance of a maze and needs to reach its centre. A Minotaur is placed deep inside the maze, which is a powerful monster capable of instantly killing the agent in melee combat. There is a wand of death placed in a random location in the maze. The agent needs to pick it up, and upon seeing the Minotaur, zap it in the direction of the monster. Once the Minotaur is killed, the agent needs to navigate itself towards the staircase (this is the default goal when RewardManager is not used). Tools such as Monodraw can help draw the map layout.

Examples

Example 1

Creating a skill task using the LevelGenerator and RewardManager.

from minihack import LevelGenerator
from minihack import RewardManager

# Define a 10x10 room and populate it with
# different objects, monster and features
lvl_gen = LevelGenerator(w=10, h=10)
lvl_gen.add_object("apple", "%")
lvl_gen.add_object("dagger", ")")
lvl_gen.add_trap(name="teleport")
lvl_gen.add_sink()
lvl_gen.add_monster("goblin")
lvl_gen.fill_terrain("rect", "L",
    0, 0, 9, 9)

# Define a reward manager
reward_manager = RewardManager()
# +1 reward and termination for eating
# an apple or wielding a dagger
reward_manager.add_eat_event("apple")
reward_manager.add_wield_event("dagger")
# -1 reward for standing on a sink
# but isn't required for terminating
# the episode
reward_manager.add_location_event("sink",
    reward=-1, terminal_required=False)

env = gym.make(
    "MiniHack-Skill-Custom-v0",
    des_file=lvl_gen.get_des(),
    reward_manager=reward_manager,
)

Example 2

Creating a MiniHack skill task using LevelGenerator with a pre-defined map layout.

from minihack import LevelGenerator

# Define the maze as a string
maze = """
--------------------
|.......|.|........|
|.-----.|.|.-----|.|
|.|...|.|.|......|.|
|.|.|.|.|.|-----.|.|
|.|.|...|....|.|.|.|
|.|.--------.|.|.|.|
|.|..........|...|.|
|.|--------------|.|
|..................|
--------------------
"""
# Set a start and goal positions
lvl_gen = LevelGenerator(map=maze)
lvl_gen.set_start_pos((9, 1))
lvl_gen.add_goal_pos((14, 5))
# Add a Minotaur at fixed position
lvl_gen.add_monster(name="minotaur",
    place=(19, 9))
# Add wand of death
lvl_gen.add_object("death", "/")

env = gym.make(
    "MiniHack-Skill-Custom-v0",
    des_file = lvl_gen.get_des(),
)