Skip to content

updates for basic support of Gymnasium (+ updates to rendering approach) #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: default
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,36 @@ As long as at least one box is on a target the RoomScore is always 0.
Sokoban has many different variations, such as: Room Size, Number of Boxes, Rendering Modes, or Rules.

#### 2.5.1 Rendering Modes
Besides the regular Sokoban rendering, each configuration can be rendered as TinyWorld, which has a pixel size equal to the grid size.
To get an environment rendered as a tiny world just add `tiny_` in front of the rendering mode. E.g: `env.render('tiny_rgb_array', scale=scale_tiny)`. Scale allows to increase the size of the rendered tiny world observation. Using scale in combination with the rendering modes, `human` or `rgb_array`, does not influence the output size.
Available rendering modes are:
Besides the regular Sokoban rendering, each configuration can be rendered as TinyWorld.

| Mode | Description |
| --- | ---
| rgb_array | Well looking 2d rgb image
| human | Displays the current state on screen
| tiny_rgb_array | Each pixel describing one element in the room
| tiny_human | Displays the tiny rgb_array on screen
To get an environment rendered as a tiny world, set the use\_tiny\_world parameter to True when instantiating the environment. E.g: `gym.make('Sokoban-v2', scale=50, use_tiny_world=True)`. Scale allows to increase the size of the rendered observation.

Rendering can only be currently done by leveraging the HumanRendering gymnasium wrapper, e.g.:

```
import gym
import gym_sokoban
from gym.wrappers import HumanRendering

SEED = 1

env = gym.make('Sokoban-v2', scale=100, use_tiny_world=True)
wrapped_env = HumanRendering(env)
obs, info = wrapped_env.reset(seed=SEED, options={})

action = 1
while True:
observation, reward, terminated, truncated, info = wrapped_env.step(action)
wrapped_env.render()

done = terminated or truncated
if done:
break

action = int(input("Enter action ==> "))

wrapped_env.close()
```

#### 2.5.2 Size Variations
The available room configurations are shown in the table below.
Expand Down
2 changes: 1 addition & 1 deletion gym_sokoban/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import pkg_resources
import json
from gym.envs.registration import register
from gymnasium.envs.registration import register

logger = logging.getLogger(__name__)

Expand Down
71 changes: 33 additions & 38 deletions gym_sokoban/envs/sokoban_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@
from .render_utils import room_to_rgb, room_to_tiny_world_rgb
import numpy as np


class SokobanEnv(gym.Env):
metadata = {
'render.modes': ['human', 'rgb_array', 'tiny_human', 'tiny_rgb_array', 'raw'],
'render_modes': ['human', 'rgb_array', 'tiny_human', 'tiny_rgb_array', 'raw']
}

def __init__(self,
dim_room=(10, 10),
scale=10,
max_steps=120,
num_boxes=4,
use_tiny_world=False,
num_gen_steps=None,
render_mode='rgb_array',
reset=True):

self.metadata = {
'render_modes': ['rgb_array'],
'render_fps': 4
}

# General Configuration
self.dim_room = dim_room
if num_gen_steps == None:
Expand All @@ -30,6 +33,14 @@ def __init__(self,
self.num_boxes = num_boxes
self.boxes_on_target = 0

# Rendering variables
self.scale = scale
self.render_mode = render_mode
self.screen_size = (500, 500)

self.window = None
self.clock = None

# Penalties and Rewards
self.penalty_for_step = -0.1
self.penalty_box_off_target = -1
Expand All @@ -38,6 +49,7 @@ def __init__(self,
self.reward_last = 0

# Other Settings
self.use_tiny_world = use_tiny_world
self.viewer = None
self.max_steps = max_steps
self.action_space = Discrete(len(ACTION_LOOKUP))
Expand All @@ -46,7 +58,7 @@ def __init__(self,

if reset:
# Initialize Room
_ = self.reset()
_ = self.reset(None)

def seed(self, seed=None):
self.np_random, seed = seeding.np_random(seed)
Expand Down Expand Up @@ -78,7 +90,7 @@ def step(self, action, observation_mode='rgb_array'):
done = self._check_if_done()

# Convert the observation to RGB frame
observation = self.render(mode=observation_mode)
observation = self.get_image(self.scale)

info = {
"action.name": ACTION_LOOKUP[action],
Expand All @@ -89,7 +101,7 @@ def step(self, action, observation_mode='rgb_array'):
info["maxsteps_used"] = self._check_if_maxsteps()
info["all_boxes_on_target"] = self._check_if_all_boxes_on_target()

return observation, self.reward_last, done, info
return observation, self.reward_last, done, False, info

def _push(self, action):
"""
Expand Down Expand Up @@ -199,7 +211,7 @@ def _check_if_all_boxes_on_target(self):
def _check_if_maxsteps(self):
return (self.max_steps == self.num_env_steps)

def reset(self, second_player=False, render_mode='rgb_array'):
def reset(self, seed=None, options={}, second_player=False, render_mode='rgb_array'):
try:
self.room_fixed, self.room_state, self.box_mapping = generate_room(
dim=self.dim_room,
Expand All @@ -210,46 +222,29 @@ def reset(self, second_player=False, render_mode='rgb_array'):
except (RuntimeError, RuntimeWarning) as e:
print("[SOKOBAN] Runtime Error/Warning: {}".format(e))
print("[SOKOBAN] Retry . . .")
return self.reset(second_player=second_player, render_mode=render_mode)
return self.reset(seed, second_player=second_player, render_mode=render_mode)

self.player_position = np.argwhere(self.room_state == 5)[0]
self.num_env_steps = 0
self.reward_last = 0
self.boxes_on_target = 0

starting_observation = self.render(render_mode)
return starting_observation

def render(self, mode='human', close=None, scale=1):
assert mode in RENDERING_MODES
starting_observation = self.get_image()
return starting_observation, {}

img = self.get_image(mode, scale)
def render(self):
img = self.get_image(self.scale)

if 'rgb_array' in mode:
return img
# repeat to scale up
img = np.repeat(img, self.scale, axis=0)
img = np.repeat(img, self.scale, axis=1)

elif 'human' in mode:
from gym.envs.classic_control import rendering
if self.viewer is None:
self.viewer = rendering.SimpleImageViewer()
self.viewer.imshow(img)
return self.viewer.isopen

elif 'raw' in mode:
arr_walls = (self.room_fixed == 0).view(np.int8)
arr_goals = (self.room_fixed == 2).view(np.int8)
arr_boxes = ((self.room_state == 4) + (self.room_state == 3)).view(np.int8)
arr_player = (self.room_state == 5).view(np.int8)

return arr_walls, arr_goals, arr_boxes, arr_player

else:
super(SokobanEnv, self).render(mode=mode) # just raise an exception
return img

def get_image(self, mode, scale=1):
def get_image(self, scale=1):

if mode.startswith('tiny_'):
img = room_to_tiny_world_rgb(self.room_state, self.room_fixed, scale=scale)
if self.use_tiny_world:
img = room_to_tiny_world_rgb(self.room_state, self.room_fixed)
else:
img = room_to_rgb(self.room_state, self.room_fixed)

Expand Down