فهرست منبع

Merge pull request #209 from rodrigodelazcano/old_gym_versions

Compatibility old gym versions, v25, v24, v23, and v22
Mark Towers 2 سال پیش
والد
کامیت
04e9a6722c

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 # Minimalistic Gridworld Environment (MiniGrid)
 
-[![Build Status](https://travis-ci.org/maximecb/gym-minigrid.svg?branch=master)](https://travis-ci.org/maximecb/gym-minigrid)
+[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://pre-commit.com/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 
 There are other gridworld Gym environments out there, but this one is
 designed to be particularly simple, lightweight and fast. The code has very few

+ 1 - 1
gym_minigrid/benchmark.py

@@ -18,7 +18,7 @@ parser.add_argument("--num_resets", default=200)
 parser.add_argument("--num_frames", default=5000)
 args = parser.parse_args()
 
-env = gym.make(args.env_name, render_mode="rgb_array")
+env = gym.make(args.env_name)
 
 # Benchmark env.reset
 t0 = time.time()

+ 6 - 1
gym_minigrid/envs/blockedunlockpickup.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Ball
+from gym_minigrid.minigrid import COLOR_NAMES, Ball, MissionSpace
 from gym_minigrid.roomgrid import RoomGrid
 
 
@@ -10,7 +10,12 @@ class BlockedUnlockPickupEnv(RoomGrid):
 
     def __init__(self, **kwargs):
         room_size = 6
+        mission_space = MissionSpace(
+            mission_func=lambda color, type: f"pick up the {color} {type}",
+            ordered_placeholders=[COLOR_NAMES, ["box", "key"]],
+        )
         super().__init__(
+            mission_space=mission_space,
             num_rows=1,
             num_cols=2,
             room_size=room_size,

+ 12 - 1
gym_minigrid/envs/crossing.py

@@ -2,7 +2,7 @@ import itertools as itt
 
 import numpy as np
 
-from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv
+from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv, MissionSpace
 
 
 class CrossingEnv(MiniGridEnv):
@@ -13,7 +13,18 @@ class CrossingEnv(MiniGridEnv):
     def __init__(self, size=9, num_crossings=1, obstacle_type=Lava, **kwargs):
         self.num_crossings = num_crossings
         self.obstacle_type = obstacle_type
+
+        if obstacle_type == Lava:
+            mission_space = MissionSpace(
+                mission_func=lambda: "avoid the lava and get to the green goal square"
+            )
+        else:
+            mission_space = MissionSpace(
+                mission_func=lambda: "find the opening and get to the green goal square"
+            )
+
         super().__init__(
+            mission_space=mission_space,
             grid_size=size,
             max_steps=4 * size * size,
             # Set this to True for maximum speed

+ 6 - 1
gym_minigrid/envs/distshift.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv
+from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv, MissionSpace
 
 
 class DistShiftEnv(MiniGridEnv):
@@ -20,7 +20,12 @@ class DistShiftEnv(MiniGridEnv):
         self.goal_pos = (width - 2, 1)
         self.strip2_row = strip2_row
 
+        mission_space = MissionSpace(
+            mission_func=lambda: "get to the green goal square"
+        )
+
         super().__init__(
+            mission_space=mission_space,
             width=width,
             height=height,
             max_steps=4 * width * height,

+ 5 - 2
gym_minigrid/envs/doorkey.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Door, Goal, Grid, Key, MiniGridEnv
+from gym_minigrid.minigrid import Door, Goal, Grid, Key, MiniGridEnv, MissionSpace
 
 
 class DoorKeyEnv(MiniGridEnv):
@@ -9,7 +9,10 @@ class DoorKeyEnv(MiniGridEnv):
     def __init__(self, size=8, **kwargs):
         if "max_steps" not in kwargs:
             kwargs["max_steps"] = 10 * size * size
-        super().__init__(grid_size=size, **kwargs)
+        mission_space = MissionSpace(
+            mission_func=lambda: "use the key to open the door and then get to the goal"
+        )
+        super().__init__(mission_space=mission_space, grid_size=size, **kwargs)
 
     def _gen_grid(self, width, height):
         # Create an empty grid

+ 7 - 1
gym_minigrid/envs/dynamicobstacles.py

@@ -2,7 +2,7 @@ from operator import add
 
 import gym
 
-from gym_minigrid.minigrid import Ball, Goal, Grid, MiniGridEnv
+from gym_minigrid.minigrid import Ball, Goal, Grid, MiniGridEnv, MissionSpace
 
 
 class DynamicObstaclesEnv(MiniGridEnv):
@@ -21,7 +21,13 @@ class DynamicObstaclesEnv(MiniGridEnv):
             self.n_obstacles = int(n_obstacles)
         else:
             self.n_obstacles = int(size / 2)
+
+        mission_space = MissionSpace(
+            mission_func=lambda: "get to the green goal square"
+        )
+
         super().__init__(
+            mission_space=mission_space,
             grid_size=size,
             max_steps=4 * size * size,
             # Set this to True for maximum speed

+ 6 - 1
gym_minigrid/envs/empty.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Goal, Grid, MiniGridEnv
+from gym_minigrid.minigrid import Goal, Grid, MiniGridEnv, MissionSpace
 
 
 class EmptyEnv(MiniGridEnv):
@@ -10,7 +10,12 @@ class EmptyEnv(MiniGridEnv):
         self.agent_start_pos = agent_start_pos
         self.agent_start_dir = agent_start_dir
 
+        mission_space = MissionSpace(
+            mission_func=lambda: "get to the green goal square"
+        )
+
         super().__init__(
+            mission_space=mission_space,
             grid_size=size,
             max_steps=4 * size * size,
             # Set this to True for maximum speed

+ 26 - 6
gym_minigrid/envs/fetch.py

@@ -1,4 +1,11 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Ball, Grid, Key, MiniGridEnv
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Ball,
+    Grid,
+    Key,
+    MiniGridEnv,
+    MissionSpace,
+)
 
 
 class FetchEnv(MiniGridEnv):
@@ -9,9 +16,24 @@ class FetchEnv(MiniGridEnv):
 
     def __init__(self, size=8, numObjs=3, **kwargs):
         self.numObjs = numObjs
-
+        self.obj_types = ["key", "ball"]
+
+        MISSION_SYNTAX = [
+            "get a",
+            "go get a",
+            "fetch a",
+            "go fetch a",
+            "you must fetch a",
+        ]
+        self.size = size
+        mission_space = MissionSpace(
+            mission_func=lambda syntax, color, type: f"{syntax} {color} {type}",
+            ordered_placeholders=[MISSION_SYNTAX, COLOR_NAMES, self.obj_types],
+        )
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=5 * size**2,
             # Set this to True for maximum speed
             see_through_walls=True,
@@ -27,13 +49,11 @@ class FetchEnv(MiniGridEnv):
         self.grid.vert_wall(0, 0)
         self.grid.vert_wall(width - 1, 0)
 
-        types = ["key", "ball"]
-
         objs = []
 
         # For each object to be generated
         while len(objs) < self.numObjs:
-            objType = self._rand_elem(types)
+            objType = self._rand_elem(self.obj_types)
             objColor = self._rand_elem(COLOR_NAMES)
 
             if objType == "key":

+ 12 - 4
gym_minigrid/envs/fourrooms.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Goal, Grid, MiniGridEnv
+from gym_minigrid.minigrid import Goal, Grid, MiniGridEnv, MissionSpace
 
 
 class FourRoomsEnv(MiniGridEnv):
@@ -10,7 +10,17 @@ class FourRoomsEnv(MiniGridEnv):
     def __init__(self, agent_pos=None, goal_pos=None, **kwargs):
         self._agent_default_pos = agent_pos
         self._goal_default_pos = goal_pos
-        super().__init__(grid_size=19, max_steps=100, **kwargs)
+
+        self.size = 19
+        mission_space = MissionSpace(mission_func=lambda: "reach the goal")
+
+        super().__init__(
+            mission_space=mission_space,
+            width=self.size,
+            height=self.size,
+            max_steps=100,
+            **kwargs
+        )
 
     def _gen_grid(self, width, height):
         # Create the grid
@@ -62,8 +72,6 @@ class FourRoomsEnv(MiniGridEnv):
         else:
             self.place_obj(Goal())
 
-        self.mission = "reach the goal"
-
     def step(self, action):
         obs, reward, done, info = MiniGridEnv.step(self, action)
         return obs, reward, done, info

+ 10 - 4
gym_minigrid/envs/gotodoor.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Door, Grid, MiniGridEnv
+from gym_minigrid.minigrid import COLOR_NAMES, Door, Grid, MiniGridEnv, MissionSpace
 
 
 class GoToDoorEnv(MiniGridEnv):
@@ -9,13 +9,19 @@ class GoToDoorEnv(MiniGridEnv):
 
     def __init__(self, size=5, **kwargs):
         assert size >= 5
-
+        self.size = size
+        mission_space = MissionSpace(
+            mission_func=lambda color: f"go to the {color} door",
+            ordered_placeholders=[COLOR_NAMES],
+        )
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=5 * size**2,
             # Set this to True for maximum speed
             see_through_walls=True,
-            **kwargs
+            **kwargs,
         )
 
     def _gen_grid(self, width, height):

+ 19 - 2
gym_minigrid/envs/gotoobject.py

@@ -1,4 +1,12 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Ball, Box, Grid, Key, MiniGridEnv
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Ball,
+    Box,
+    Grid,
+    Key,
+    MiniGridEnv,
+    MissionSpace,
+)
 
 
 class GoToObjectEnv(MiniGridEnv):
@@ -9,9 +17,18 @@ class GoToObjectEnv(MiniGridEnv):
 
     def __init__(self, size=6, numObjs=2, **kwargs):
         self.numObjs = numObjs
+        self.size = size
+        # Types of objects to be generated
+        self.obj_types = ["key", "ball", "box"]
 
+        mission_space = MissionSpace(
+            mission_func=lambda color, type: f"go to the {color} {type}",
+            ordered_placeholders=[COLOR_NAMES, self.obj_types],
+        )
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=5 * size**2,
             # Set this to True for maximum speed
             see_through_walls=True,

+ 6 - 1
gym_minigrid/envs/keycorridor.py

@@ -1,3 +1,4 @@
+from gym_minigrid.minigrid import COLOR_NAMES, MissionSpace
 from gym_minigrid.roomgrid import RoomGrid
 
 
@@ -9,8 +10,12 @@ class KeyCorridorEnv(RoomGrid):
 
     def __init__(self, num_rows=3, obj_type="ball", room_size=6, **kwargs):
         self.obj_type = obj_type
-
+        mission_space = MissionSpace(
+            mission_func=lambda color: f"pick up the {color} {obj_type}",
+            ordered_placeholders=[COLOR_NAMES],
+        )
         super().__init__(
+            mission_space=mission_space,
             room_size=room_size,
             num_rows=num_rows,
             max_steps=30 * room_size**2,

+ 15 - 3
gym_minigrid/envs/lavagap.py

@@ -1,6 +1,6 @@
 import numpy as np
 
-from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv
+from gym_minigrid.minigrid import Goal, Grid, Lava, MiniGridEnv, MissionSpace
 
 
 class LavaGapEnv(MiniGridEnv):
@@ -11,12 +11,24 @@ class LavaGapEnv(MiniGridEnv):
 
     def __init__(self, size, obstacle_type=Lava, **kwargs):
         self.obstacle_type = obstacle_type
+        self.size = size
+
+        if obstacle_type == Lava:
+            mission_space = MissionSpace(
+                mission_func=lambda: "avoid the lava and get to the green goal square"
+            )
+        else:
+            mission_space = MissionSpace(
+                mission_func=lambda: "find the opening and get to the green goal square"
+            )
+
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=4 * size * size,
             # Set this to True for maximum speed
             see_through_walls=False,
-            **kwargs
         )
 
     def _gen_grid(self, width, height):

+ 22 - 2
gym_minigrid/envs/lockedroom.py

@@ -1,4 +1,13 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Door, Goal, Grid, Key, MiniGridEnv, Wall
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Door,
+    Goal,
+    Grid,
+    Key,
+    MiniGridEnv,
+    MissionSpace,
+    Wall,
+)
 
 
 class LockedRoom:
@@ -22,7 +31,18 @@ class LockedRoomEnv(MiniGridEnv):
     """
 
     def __init__(self, size=19, **kwargs):
-        super().__init__(grid_size=size, max_steps=10 * size, **kwargs)
+        self.size = size
+        mission_space = MissionSpace(
+            mission_func=lambda lockedroom_color, keyroom_color, door_color: f"get the {lockedroom_color} key from the {keyroom_color} room, unlock the {door_color} door and go to the goal",
+            ordered_placeholders=[COLOR_NAMES] * 3,
+        )
+        super().__init__(
+            mission_space=mission_space,
+            width=size,
+            height=size,
+            max_steps=10 * size,
+            **kwargs,
+        )
 
     def _gen_grid(self, width, height):
         # Create the grid

+ 8 - 2
gym_minigrid/envs/memory.py

@@ -1,6 +1,6 @@
 import numpy as np
 
-from gym_minigrid.minigrid import Ball, Grid, Key, MiniGridEnv, Wall
+from gym_minigrid.minigrid import Ball, Grid, Key, MiniGridEnv, MissionSpace, Wall
 
 
 class MemoryEnv(MiniGridEnv):
@@ -14,9 +14,15 @@ class MemoryEnv(MiniGridEnv):
     """
 
     def __init__(self, size=8, random_length=False, **kwargs):
+        self.size = size
         self.random_length = random_length
+        mission_space = MissionSpace(
+            mission_func=lambda: "go to the matching object at the end of the hallway"
+        )
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=5 * size**2,
             # Set this to True for maximum speed
             see_through_walls=False,

+ 21 - 2
gym_minigrid/envs/multiroom.py

@@ -1,4 +1,12 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Door, Goal, Grid, MiniGridEnv, Wall
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Door,
+    Goal,
+    Grid,
+    MiniGridEnv,
+    MissionSpace,
+    Wall,
+)
 
 
 class MultiRoom:
@@ -25,7 +33,18 @@ class MultiRoomEnv(MiniGridEnv):
 
         self.rooms = []
 
-        super().__init__(grid_size=25, max_steps=self.maxNumRooms * 20, **kwargs)
+        mission_space = MissionSpace(
+            mission_func=lambda: "traverse the rooms to get to the goal"
+        )
+
+        self.size = 25
+
+        super().__init__(
+            mission_space=mission_space,
+            width=self.size,
+            height=self.size,
+            max_steps=self.maxNumRooms * 20,
+        )
 
     def _gen_grid(self, width, height):
         roomList = []

+ 8 - 4
gym_minigrid/envs/obstructedmaze.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import COLOR_NAMES, DIR_TO_VEC, Ball, Box, Key
+from gym_minigrid.minigrid import COLOR_NAMES, DIR_TO_VEC, Ball, Box, Key, MissionSpace
 from gym_minigrid.roomgrid import RoomGrid
 
 
@@ -12,12 +12,16 @@ class ObstructedMazeEnv(RoomGrid):
         room_size = 6
         max_steps = 4 * num_rooms_visited * room_size**2
 
+        mission_space = MissionSpace(
+            mission_func=lambda: f"pick up the {COLOR_NAMES[0]} ball",
+        )
         super().__init__(
+            mission_space=mission_space,
             room_size=room_size,
             num_rows=num_rows,
             num_cols=num_cols,
             max_steps=max_steps,
-            **kwargs
+            **kwargs,
         )
 
     def _gen_grid(self, width, height):
@@ -121,7 +125,7 @@ class ObstructedMaze_Full(ObstructedMazeEnv):
         blocked=True,
         num_quarters=4,
         num_rooms_visited=25,
-        **kwargs
+        **kwargs,
     ):
         self.agent_room = agent_room
         self.key_in_box = key_in_box
@@ -155,7 +159,7 @@ class ObstructedMaze_Full(ObstructedMazeEnv):
                     door_idx=(i + k) % 4,
                     color=self.door_colors[(i + k) % len(self.door_colors)],
                     key_in_box=self.key_in_box,
-                    blocked=self.blocked
+                    blocked=self.blocked,
                 )
 
         corners = [(2, 0), (2, 2), (0, 2), (0, 0)][: self.num_quarters]

+ 19 - 2
gym_minigrid/envs/playground.py

@@ -1,4 +1,13 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Ball, Box, Door, Grid, Key, MiniGridEnv
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Ball,
+    Box,
+    Door,
+    Grid,
+    Key,
+    MiniGridEnv,
+    MissionSpace,
+)
 
 
 class PlaygroundEnv(MiniGridEnv):
@@ -8,7 +17,15 @@ class PlaygroundEnv(MiniGridEnv):
     """
 
     def __init__(self, **kwargs):
-        super().__init__(grid_size=19, max_steps=100, **kwargs)
+        mission_space = MissionSpace(mission_func=lambda: "")
+        self.size = 19
+        super().__init__(
+            mission_space=mission_space,
+            width=self.size,
+            height=self.size,
+            max_steps=100,
+            **kwargs
+        )
 
     def _gen_grid(self, width, height):
         # Create the grid

+ 23 - 4
gym_minigrid/envs/putnear.py

@@ -1,4 +1,12 @@
-from gym_minigrid.minigrid import COLOR_NAMES, Ball, Box, Grid, Key, MiniGridEnv
+from gym_minigrid.minigrid import (
+    COLOR_NAMES,
+    Ball,
+    Box,
+    Grid,
+    Key,
+    MiniGridEnv,
+    MissionSpace,
+)
 
 
 class PutNearEnv(MiniGridEnv):
@@ -8,14 +16,25 @@ class PutNearEnv(MiniGridEnv):
     """
 
     def __init__(self, size=6, numObjs=2, **kwargs):
+        self.size = size
         self.numObjs = numObjs
-
+        self.obj_types = ["key", "ball", "box"]
+        mission_space = MissionSpace(
+            mission_func=lambda move_color, move_type, target_color, target_type: f"put the {move_color} {move_type} near the {target_color} {target_type}",
+            ordered_placeholders=[
+                COLOR_NAMES,
+                self.obj_types,
+                COLOR_NAMES,
+                self.obj_types,
+            ],
+        )
         super().__init__(
-            grid_size=size,
+            mission_space=mission_space,
+            width=size,
+            height=size,
             max_steps=5 * size,
             # Set this to True for maximum speed
             see_through_walls=True,
-            **kwargs
         )
 
     def _gen_grid(self, width, height):

+ 9 - 3
gym_minigrid/envs/redbluedoors.py

@@ -1,4 +1,4 @@
-from gym_minigrid.minigrid import Door, Grid, MiniGridEnv
+from gym_minigrid.minigrid import Door, Grid, MiniGridEnv, MissionSpace
 
 
 class RedBlueDoorEnv(MiniGridEnv):
@@ -10,9 +10,15 @@ class RedBlueDoorEnv(MiniGridEnv):
 
     def __init__(self, size=8, **kwargs):
         self.size = size
-
+        mission_space = MissionSpace(
+            mission_func=lambda: "open the red door then the blue door"
+        )
         super().__init__(
-            width=2 * size, height=size, max_steps=20 * size * size, **kwargs
+            mission_space=mission_space,
+            width=2 * size,
+            height=size,
+            max_steps=20 * size * size,
+            **kwargs
         )
 
     def _gen_grid(self, width, height):

+ 3 - 0
gym_minigrid/envs/unlock.py

@@ -1,3 +1,4 @@
+from gym_minigrid.minigrid import MissionSpace
 from gym_minigrid.roomgrid import RoomGrid
 
 
@@ -8,7 +9,9 @@ class UnlockEnv(RoomGrid):
 
     def __init__(self, **kwargs):
         room_size = 6
+        mission_space = MissionSpace(mission_func=lambda: "open the door")
         super().__init__(
+            mission_space=mission_space,
             num_rows=1,
             num_cols=2,
             room_size=room_size,

+ 6 - 0
gym_minigrid/envs/unlockpickup.py

@@ -1,3 +1,4 @@
+from gym_minigrid.minigrid import COLOR_NAMES, MissionSpace
 from gym_minigrid.roomgrid import RoomGrid
 
 
@@ -8,7 +9,12 @@ class UnlockPickupEnv(RoomGrid):
 
     def __init__(self, **kwargs):
         room_size = 6
+        mission_space = MissionSpace(
+            mission_func=lambda color: f"pick up the {color} box",
+            ordered_placeholders=[COLOR_NAMES],
+        )
         super().__init__(
+            mission_space=mission_space,
             num_rows=1,
             num_cols=2,
             room_size=room_size,

+ 1 - 1
gym_minigrid/manual_control.py

@@ -93,7 +93,7 @@ parser.add_argument(
 
 args = parser.parse_args()
 
-env = gym.make(args.env, render_mode="rgb_array")
+env = gym.make(args.env)
 
 if args.agent_view:
     env = RGBImgPartialObsWrapper(env)

+ 204 - 37
gym_minigrid/minigrid.py

@@ -1,14 +1,13 @@
 import hashlib
 import math
-import string
 from abc import abstractmethod
 from enum import IntEnum
-from functools import partial
+from typing import Any, Callable, Optional, Union
 
 import gym
 import numpy as np
 from gym import spaces
-from gym.utils.renderer import Renderer
+from gym.utils import seeding
 
 # Size in pixels of a tile in the full-scale human view
 from gym_minigrid.rendering import (
@@ -79,6 +78,197 @@ DIR_TO_VEC = [
 ]
 
 
+def check_if_no_duplicate(duplicate_list: list) -> bool:
+    """Check if given list contains any duplicates"""
+    return len(set(duplicate_list)) == len(duplicate_list)
+
+
+class MissionSpace(spaces.Space[str]):
+    r"""A space representing a mission for the Gym-Minigrid environments.
+    The space allows generating random mission strings constructed with an input placeholder list.
+    Example Usage::
+        >>> observation_space = MissionSpace(mission_func=lambda color: f"Get the {color} ball.",
+                                                ordered_placeholders=[["green", "blue"]])
+        >>> observation_space.sample()
+            "Get the green ball."
+        >>> observation_space = MissionSpace(mission_func=lambda : "Get the ball.".,
+                                                ordered_placeholders=None)
+        >>> observation_space.sample()
+            "Get the ball."
+    """
+
+    def __init__(
+        self,
+        mission_func: Callable[..., str],
+        ordered_placeholders: Optional["list[list[str]]"] = None,
+        seed: Optional[Union[int, seeding.RandomNumberGenerator]] = None,
+    ):
+        r"""Constructor of :class:`MissionSpace` space.
+
+        Args:
+            mission_func (lambda _placeholders(str): _mission(str)): Function that generates a mission string from random placeholders.
+            ordered_placeholders (Optional["list[list[str]]"]): List of lists of placeholders ordered in placing order in the mission function mission_func.
+            seed: seed: The seed for sampling from the space.
+        """
+        # Check that the ordered placeholders and mission function are well defined.
+        if ordered_placeholders is not None:
+            assert (
+                len(ordered_placeholders) == mission_func.__code__.co_argcount
+            ), f"The number of placeholders {len(ordered_placeholders)} is different from the number of parameters in the mission function {mission_func.__code__.co_argcount}."
+            for placeholder_list in ordered_placeholders:
+                assert check_if_no_duplicate(
+                    placeholder_list
+                ), "Make sure that the placeholders don't have any duplicate values."
+        else:
+            assert (
+                mission_func.__code__.co_argcount == 0
+            ), f"If the ordered placeholders are {ordered_placeholders}, the mission function shouldn't have any parameters."
+
+        self.ordered_placeholders = ordered_placeholders
+        self.mission_func = mission_func
+
+        super().__init__(dtype=str, seed=seed)
+
+        # Check that mission_func returns a string
+        sampled_mission = self.sample()
+        assert isinstance(
+            sampled_mission, str
+        ), f"mission_func must return type str not {type(sampled_mission)}"
+
+    def sample(self) -> str:
+        """Sample a random mission string."""
+        if self.ordered_placeholders is not None:
+            placeholders = []
+            for rand_var_list in self.ordered_placeholders:
+                idx = self.np_random.integers(0, len(rand_var_list))
+
+                placeholders.append(rand_var_list[idx])
+
+            return self.mission_func(*placeholders)
+        else:
+            return self.mission_func()
+
+    def contains(self, x: Any) -> bool:
+        """Return boolean specifying if x is a valid member of this space."""
+        # Store a list of all the placeholders from self.ordered_placeholders that appear in x
+        if self.ordered_placeholders is not None:
+            check_placeholder_list = []
+            for placeholder_list in self.ordered_placeholders:
+                for placeholder in placeholder_list:
+                    if placeholder in x:
+                        check_placeholder_list.append(placeholder)
+
+            # Remove duplicates from the list
+            check_placeholder_list = list(set(check_placeholder_list))
+
+            start_id_placeholder = []
+            end_id_placeholder = []
+            # Get the starting and ending id of the identified placeholders with possible duplicates
+            new_check_placeholder_list = []
+            for placeholder in check_placeholder_list:
+                new_start_id_placeholder = [
+                    i for i in range(len(x)) if x.startswith(placeholder, i)
+                ]
+                new_check_placeholder_list += [placeholder] * len(
+                    new_start_id_placeholder
+                )
+                end_id_placeholder += [
+                    start_id + len(placeholder) - 1
+                    for start_id in new_start_id_placeholder
+                ]
+                start_id_placeholder += new_start_id_placeholder
+
+            # Order by starting id the placeholders
+            ordered_placeholder_list = sorted(
+                zip(
+                    start_id_placeholder, end_id_placeholder, new_check_placeholder_list
+                )
+            )
+
+            # Check for repeated placeholders contained in each other
+            remove_placeholder_id = []
+            for i, placeholder_1 in enumerate(ordered_placeholder_list):
+                starting_id = i + 1
+                for j, placeholder_2 in enumerate(
+                    ordered_placeholder_list[starting_id:]
+                ):
+                    # Check if place holder ids overlap and keep the longest
+                    if max(placeholder_1[0], placeholder_2[0]) < min(
+                        placeholder_1[1], placeholder_2[1]
+                    ):
+                        remove_placeholder = min(
+                            placeholder_1[2], placeholder_2[2], key=len
+                        )
+                        if remove_placeholder == placeholder_1[2]:
+                            remove_placeholder_id.append(i)
+                        else:
+                            remove_placeholder_id.append(i + j + 1)
+            for id in remove_placeholder_id:
+                del ordered_placeholder_list[id]
+
+            final_placeholders = [
+                placeholder[2] for placeholder in ordered_placeholder_list
+            ]
+
+            # Check that the identified final placeholders are in the same order as the original placeholders.
+            for orered_placeholder, final_placeholder in zip(
+                self.ordered_placeholders, final_placeholders
+            ):
+                if final_placeholder in orered_placeholder:
+                    continue
+                else:
+                    return False
+            try:
+                mission_string_with_placeholders = self.mission_func(
+                    *final_placeholders
+                )
+            except Exception as e:
+                print(
+                    f"{x} is not contained in MissionSpace due to the following exception: {e}"
+                )
+                return False
+
+            return bool(mission_string_with_placeholders == x)
+
+        else:
+            return bool(self.mission_func() == x)
+
+    def __repr__(self) -> str:
+        """Gives a string representation of this space."""
+        return f"MissionSpace({self.mission_func}, {self.ordered_placeholders})"
+
+    def __eq__(self, other) -> bool:
+        """Check whether ``other`` is equivalent to this instance."""
+        if isinstance(other, MissionSpace):
+
+            # Check that place holder lists are the same
+            if self.ordered_placeholders is not None:
+                # Check length
+                if (len(self.order_placeholder) == len(other.order_placeholder)) and (
+                    all(
+                        set(i) == set(j)
+                        for i, j in zip(self.order_placeholder, other.order_placeholder)
+                    )
+                ):
+                    # Check mission string is the same with dummy space placeholders
+                    test_placeholders = [""] * len(self.order_placeholder)
+                    mission = self.mission_func(*test_placeholders)
+                    other_mission = other.mission_func(*test_placeholders)
+                    return mission == other_mission
+            else:
+
+                # Check that other is also None
+                if other.ordered_placeholders is None:
+
+                    # Check mission string is the same
+                    mission = self.mission_func()
+                    other_mission = other.mission_func()
+                    return mission == other_mission
+
+        # If none of the statements above return then False
+        return False
+
+
 class WorldObj:
     """
     Base class for grid world objects
@@ -261,9 +451,7 @@ class Door(WorldObj):
             state = 1
         else:
             raise ValueError(
-                "There is no possible state encoding for the state:\n -Door Open: {}\n -Door Closed: {}\n -Door Locked: {}".format(
-                    self.is_open, not self.is_open, self.is_locked
-                )
+                f"There is no possible state encoding for the state:\n -Door Open: {self.is_open}\n -Door Closed: {not self.is_open}\n -Door Locked: {self.is_locked}"
             )
 
         return (OBJECT_TO_IDX[self.type], COLOR_TO_IDX[self.color], state)
@@ -663,17 +851,21 @@ class MiniGridEnv(gym.Env):
 
     def __init__(
         self,
+        mission_space: MissionSpace,
         grid_size: int = None,
         width: int = None,
         height: int = None,
         max_steps: int = 100,
         see_through_walls: bool = False,
         agent_view_size: int = 7,
-        render_mode: str = None,
         highlight: bool = True,
         tile_size: int = TILE_PIXELS,
-        **kwargs
+        **kwargs,
     ):
+
+        # Initialize mission
+        self.mission = mission_space.sample()
+
         # Can't set both grid_size and width/height
         if grid_size:
             assert width is None and height is None
@@ -693,7 +885,7 @@ class MiniGridEnv(gym.Env):
 
         # Observations are dictionaries containing an
         # encoding of the grid and a textual 'mission' string
-        self.observation_space = spaces.Box(
+        image_observation_space = spaces.Box(
             low=0,
             high=255,
             shape=(self.agent_view_size, self.agent_view_size, 3),
@@ -701,24 +893,12 @@ class MiniGridEnv(gym.Env):
         )
         self.observation_space = spaces.Dict(
             {
-                "image": self.observation_space,
+                "image": image_observation_space,
                 "direction": spaces.Discrete(4),
-                "mission": spaces.Text(
-                    max_length=200,
-                    charset=string.ascii_letters + string.digits + " .,!-",
-                ),
+                "mission": mission_space,
             }
         )
 
-        # render mode
-        self.render_mode = render_mode
-        render_frame = partial(
-            self._render,
-            highlight=highlight,
-            tile_size=tile_size,
-        )
-        self.renderer = Renderer(self.render_mode, render_frame)
-
         # Range of possible rewards
         self.reward_range = (0, 1)
 
@@ -763,8 +943,6 @@ class MiniGridEnv(gym.Env):
         # Return first observation
         obs = self.gen_obs()
 
-        self.renderer.reset()
-        self.renderer.render_step()
         if not return_info:
             return obs
         else:
@@ -1179,7 +1357,6 @@ class MiniGridEnv(gym.Env):
 
         obs = self.gen_obs()
 
-        self.renderer.render_step()
         return obs, reward, done, {}
 
     def gen_obs_grid(self, agent_view_size=None):
@@ -1258,7 +1435,7 @@ class MiniGridEnv(gym.Env):
 
         return img
 
-    def _render(self, mode="human", highlight=True, tile_size=TILE_PIXELS):
+    def render(self, mode="human", highlight=True, tile_size=TILE_PIXELS):
         assert mode in self.metadata["render_modes"]
         """
         Render the whole-grid human view
@@ -1315,16 +1492,6 @@ class MiniGridEnv(gym.Env):
         else:
             return img
 
-    def render(self, mode="human", close=False, highlight=True, tile_size=TILE_PIXELS):
-        if close:
-            raise Exception(
-                "Please close the rendering window using env.close(). Closing the rendering window with the render method is no longer allowed."
-            )
-        if self.render_mode is not None:
-            return self.renderer.get_renders()
-        else:
-            return self._render(mode, highlight=highlight, tile_size=tile_size)
-
     def close(self):
         if self.window:
             self.window.close()

+ 1 - 3
gym_minigrid/wrappers.py

@@ -181,9 +181,7 @@ class RGBImgObsWrapper(ObservationWrapper):
     def observation(self, obs):
         env = self.unwrapped
 
-        rgb_img = env._render(
-            mode="rgb_array", highlight=True, tile_size=self.tile_size
-        )
+        rgb_img = env.render(mode="rgb_array", highlight=True, tile_size=self.tile_size)
 
         return {**obs, "image": rgb_img}
 

+ 51 - 4
tests/test_envs.py

@@ -4,7 +4,7 @@ import pytest
 from gym.envs.registration import EnvSpec
 from gym.utils.env_checker import check_env
 
-from gym_minigrid.minigrid import Grid
+from gym_minigrid.minigrid import Grid, MissionSpace
 from tests.utils import all_testing_env_specs, assert_equals
 
 CHECK_ENV_IGNORE_WARNINGS = [
@@ -101,11 +101,11 @@ def test_render_modes(spec):
 
     for mode in env.metadata.get("render_modes", []):
         if mode != "human":
-            new_env = spec.make(render_mode=mode)
+            new_env = spec.make()
 
             new_env.reset()
             new_env.step(new_env.action_space.sample())
-            new_env.render()
+            new_env.render(mode=mode)
 
 
 @pytest.mark.parametrize("env_id", ["MiniGrid-DoorKey-6x6-v0"])
@@ -192,7 +192,7 @@ def old_run_test(env_spec):
 
 @pytest.mark.parametrize("env_id", ["MiniGrid-Empty-8x8-v0"])
 def test_interactive_mode(env_id):
-    env = gym.make(env_id, render_mode="human")
+    env = gym.make(env_id)
     env.reset()
 
     for i in range(0, 100):
@@ -205,3 +205,50 @@ def test_interactive_mode(env_id):
 
     # Test the close method
     env.close()
+
+
+def test_mission_space():
+
+    # Test placeholders
+    mission_space = MissionSpace(
+        mission_func=lambda color, obj_type: f"Get the {color} {obj_type}.",
+        ordered_placeholders=[["green", "red"], ["ball", "key"]],
+    )
+
+    assert mission_space.contains("Get the green ball.")
+    assert mission_space.contains("Get the red key.")
+    assert not mission_space.contains("Get the purple box.")
+
+    # Test passing inverted placeholders
+    assert not mission_space.contains("Get the key red.")
+
+    # Test passing extra repeated placeholders
+    assert not mission_space.contains("Get the key red key.")
+
+    # Test contained placeholders like "get the" and "go get the". "get the" string is contained in both placeholders.
+    mission_space = MissionSpace(
+        mission_func=lambda get_syntax, obj_type: f"{get_syntax} {obj_type}.",
+        ordered_placeholders=[
+            ["go get the", "get the", "go fetch the", "fetch the"],
+            ["ball", "key"],
+        ],
+    )
+
+    assert mission_space.contains("get the ball.")
+    assert mission_space.contains("go get the key.")
+    assert mission_space.contains("go fetch the ball.")
+
+    # Test repeated placeholders
+    mission_space = MissionSpace(
+        mission_func=lambda get_syntax, color_1, obj_type_1, color_2, obj_type_2: f"{get_syntax} {color_1} {obj_type_1} and the {color_2} {obj_type_2}.",
+        ordered_placeholders=[
+            ["go get the", "get the", "go fetch the", "fetch the"],
+            ["green", "red"],
+            ["ball", "key"],
+            ["green", "red"],
+            ["ball", "key"],
+        ],
+    )
+
+    assert mission_space.contains("get the green key and the green key.")
+    assert mission_space.contains("go fetch the red ball and the green key.")