Tutorial on Creating Environments#

In this tutorial, we will go through the process of creating a new environment.

Boilerplate Code#

class SimpleEnv(MiniGridEnv):
    def __init__(
        self,
        size=8,
        agent_start_pos=(1, 1),
        agent_start_dir=0,
        max_steps: int | None = None,
        **kwargs,
    ):
        self.agent_start_pos = agent_start_pos
        self.agent_start_dir = agent_start_dir

        mission_space = MissionSpace(mission_func=self._gen_mission)

        super().__init__(
            mission_space=mission_space,
            grid_size=size,
            max_steps=256,
            **kwargs,
        )

    @staticmethod
    def _gen_mission():
        return "grand mission"

First, we need to create a class that inherits from MiniGridEnv, we call our class SimpleEnv. Then, we define a mission space, the recommended way to do it is to define a static function.

@staticmethod
def _gen_mission():
    return "grand mission"

that only returns a string which corresponds to the mission. We then pass this function as an argument

mission_space = MissionSpace(mission_func=self._gen_mission)

Then, in the __init__ function, we pass the required arguments to the parent class. In this case we are passing the mission_space, grid_size and max_steps. We also create self.agent_start_pos and self.agent_start_dir so that member functions can have access to these two values.

Generate the grid-world#

To create your own grid-world environment we override the function _gen_grid. We can see from the MiniGridEnv class

# MiniGridEnv._gen_grid
@abstractmethod
def _gen_grid(self, width, height):
    pass

_gen_grid takes in two inputs width and height, which are used to specify the size of the environment.

Create World#

To create the environment, we first an empty grid using

self.grid = Grid(width, height)

Then, we create the walls that surrounds the grid

self.grid.wall_rect(0, 0, width, height)

Finally, we place the agent in the environment

if self.agent_start_pos is not None:
    self.agent_pos = self.agent_start_pos
    self.agent_dir = self.agent_start_dir
else:
    self.place_agent()

these lines of code is saying if we specified the agent starting position and direction the environment will follow what we specified, otherwise, it will randomly place the agent within the environment. If we render the environment right now, it would look like this:

env after first step

Place Goal#

To place a goal in the environment, we use the function

self.put_obj(Goal(), width - 2, height - 2)

which places the goal in the bottom right corner. Now the environment should look like this:

env after second step

Create Separating Walls#

To create a wall that separates the environment into two rooms, we use the command

for i in range(0, height):
    self.grid.set(5, i, Wall())

this goes over the grids with coordinates (5, 0), (5, 1)(5, height) and make them walls. The result would look like:

env after third step

Add Keys and Doors#

To add keys and doors, we would first need to import

from minigrid.core.constants import COLOR_NAMES
from minigrid.core.world_object import Door, Key

Then, we can simply place the door and key using the command

self.grid.set(5, 6, Door(COLOR_NAMES[0], is_locked=True))
self.grid.set(3, 6, Key(COLOR_NAMES[0]))

Now the environment looks like:

env after fourth step

Even for creating more complicated environments, this is all you need know.

Source Code#

The source code of this tutorial is

from __future__ import annotations

from minigrid.core.constants import COLOR_NAMES
from minigrid.core.grid import Grid
from minigrid.core.mission import MissionSpace
from minigrid.core.world_object import Door, Goal, Key, Wall
from minigrid.manual_control import ManualControl
from minigrid.minigrid_env import MiniGridEnv


class SimpleEnv(MiniGridEnv):
    def __init__(
        self,
        size=10,
        agent_start_pos=(1, 1),
        agent_start_dir=0,
        max_steps: int | None = None,
        **kwargs,
    ):
        self.agent_start_pos = agent_start_pos
        self.agent_start_dir = agent_start_dir

        mission_space = MissionSpace(mission_func=self._gen_mission)

        if max_steps is None:
            max_steps = 4 * size**2

        super().__init__(
            mission_space=mission_space,
            grid_size=size,
            # Set this to True for maximum speed
            see_through_walls=True,
            max_steps=max_steps,
            **kwargs,
        )

    @staticmethod
    def _gen_mission():
        return "grand mission"

    def _gen_grid(self, width, height):
        # Create an empty grid
        self.grid = Grid(width, height)

        # Generate the surrounding walls
        self.grid.wall_rect(0, 0, width, height)

        # Generate vertical separation wall
        for i in range(0, height):
            self.grid.set(5, i, Wall())
        
        # Place the door and key
        self.grid.set(5, 6, Door(COLOR_NAMES[0], is_locked=True))
        self.grid.set(3, 6, Key(COLOR_NAMES[0]))

        # Place a goal square in the bottom-right corner
        self.put_obj(Goal(), width - 2, height - 2)

        # Place the agent
        if self.agent_start_pos is not None:
            self.agent_pos = self.agent_start_pos
            self.agent_dir = self.agent_start_dir
        else:
            self.place_agent()

        self.mission = "grand mission"


def main():
    env = SimpleEnv(render_mode="human")

    # enable manual control for testing
    manual_control = ManualControl(env, seed=42)
    manual_control.start()

    
if __name__ == "__main__":
    main()