Skip to content

Commit

Permalink
Merge branch 'release/1.2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bam4d committed Jun 28, 2021
2 parents f4e42e9 + e48daf5 commit f9fe6bb
Show file tree
Hide file tree
Showing 49 changed files with 1,519 additions and 214 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
- OS: [e.g. mac/linux/windows]
- Version [e.g. 1.2.0]
- Version [e.g. 1.2.1]

**Additional context**
Add any other context about the problem here.
4 changes: 2 additions & 2 deletions readthedocs.yml → .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ sphinx:

# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml
# configuration: mkdocs.yml

# Optionally build your docs in additional formats such as PDF
formats:
- pdf

# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
version: 3.8
install:
- requirements: docs/requirements.txt
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10.0)
project(Griddly VERSION 1.2.0)
project(Griddly VERSION 1.2.1)

set(BINARY ${CMAKE_PROJECT_NAME})

Expand Down
3 changes: 2 additions & 1 deletion bindings/python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace griddly {

PYBIND11_MODULE(python_griddly, m) {
m.doc() = "Griddly python bindings";
m.attr("version") = "1.2.0";
m.attr("version") = "1.2.1";

#ifndef NDEBUG
spdlog::set_level(spdlog::level::debug);
Expand Down Expand Up @@ -107,6 +107,7 @@ PYBIND11_MODULE(python_griddly, m) {
observer_type.value("BLOCK_2D", ObserverType::BLOCK_2D);
observer_type.value("ISOMETRIC", ObserverType::ISOMETRIC);
observer_type.value("VECTOR", ObserverType::VECTOR);
observer_type.value("ASCII", ObserverType::ASCII);

py::class_<NumpyWrapper<uint8_t>, std::shared_ptr<NumpyWrapper<uint8_t>>>(m, "Observation", py::buffer_protocol())
.def_buffer([](NumpyWrapper<uint8_t> &m) -> py::buffer_info {
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Chris Bamford'

# The full version, including alpha/beta/rc tags
release = '1.2.0'
release = '1.2.1'


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/action spaces/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ In order to easily support games with large action spaces such as RTS games, sev

If ``mask_type == 'full'`` then a mask of dimensions (grid_height, grid_width) is returned. This mask can be used in the case where a one-hot representation of the entire grid is used for location selection.

If ``mask_type == 'reduced'`` then two masks are returned. One for ``grid_height`` and one for ``grid_width``. This mask can be used when two seperate one-hot representations are used for ``x`` and ``y`` selection.
If ``mask_type == 'reduced'`` then two masks are returned. One for ``grid_height`` and one for ``grid_width``. This mask can be used when two separate one-hot representations are used for ``x`` and ``y`` selection.

.. warning:: player_id=0 is reserved for NPCs and internal actions

Expand Down
5 changes: 3 additions & 2 deletions docs/getting-started/procedural content generation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ This is a basic example and generating levels for this environment might not be
Clusters Level Generator
************************

A much more complicated example would be to use the `Clusters<doc_clusters>` game and generate new levels. The aim of the Clusters game is for the agent to push coloured blocks together to form "clusters", whilst avoiding spikes.
A much more complicated example would be to use the :ref:`Clusters <doc_clusters>` game and generate new levels. The aim of the Clusters game is for the agent to push coloured blocks together to form "clusters", whilst avoiding spikes.
The game is fully deterministic and there are only 5 levels supplied in the original GDY file. This makes it a perfect candidate for building new levels and testing if Reinforcement Learning can still solve these levels!


Level Generator Class
=====================

Here's an example of a level generator for the cluster's game.
Here's an example of a level generator for the cluster's game. Levels are generated with simple configurable heuristics such as maximum number of each coloured boxes and maximum numbers of spikes.
The boxes and spikes are randomly placed in the grid to create the initial game layout. The agent is also added to the grid in a random position.

The ``LevelGenerator`` class can be used as a base class. Only the ``generate`` function needs to be implemented.

Expand Down
6 changes: 3 additions & 3 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
sphinx==3.1.0
sphinx==4.0.2
recommonmark==0.6.0
sphinx_rtd_theme==0.4.3
sphinxcontrib-images==0.9.2
sphinx_rtd_theme==0.5.2
sphinxcontrib-images==0.9.3
sphinx-copybutton==0.3.1
2 changes: 1 addition & 1 deletion docs/rllib/rts/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Full Example
<div class="figure align-center" id="vid1">
<video onloadeddata="this.play();" playsinline loop muted width="50%">

<source src="/_static/video/griddly_rts.mp4"
<source src="../../_static/video/griddly_rts.mp4"
type="video/mp4">

Sorry, your browser doesn't support embedded videos.
Expand Down
Binary file modified python/examples/griddlyrts/griddly_rts_global.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified python/examples/griddlyrts/griddly_rts_p1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified python/examples/griddlyrts/griddly_rts_p2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions python/examples/snippet.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

def make_env(name):
wrapper = GymWrapperFactory()
wrapper.build_gym_from_yaml(name, 'Single-Player/Mini-Grid/minigrid-spiders.yaml',
wrapper.build_gym_from_yaml(name, 'Single-Player/GVGAI/spider-nest.yaml',
player_observer_type=gd.ObserverType.SPRITE_2D,
global_observer_type=gd.ObserverType.BLOCK_2D,
global_observer_type=gd.ObserverType.ASCII,
level=0,
max_steps=200)

Expand Down Expand Up @@ -38,8 +38,8 @@ def make_env(name):

frames += 1
obs, reward, done, info = env.step(action)
#env.render()
env.render(observer='global')
env.render()
print(env.render(observer='global'))

if frames % 1000 == 0:
end = timer()
Expand Down
43 changes: 40 additions & 3 deletions python/griddly/GymWrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class GymWrapper(gym.Env):
metadata = {'render.modes': ['human', 'rgb_array']}

def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType.VECTOR,
def __init__(self, yaml_file=None, yaml_string=None, level=0, global_observer_type=gd.ObserverType.VECTOR,
player_observer_type=gd.ObserverType.VECTOR, max_steps=None, gdy_path=None, image_path=None,
shader_path=None,
gdy=None, game=None, **kwargs):
Expand All @@ -29,10 +29,14 @@ def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType
self._renderWindow = {}

# If we are loading a yaml file
if yaml_file is not None:
if yaml_file is not None or yaml_string is not None:
self._is_clone = False
loader = GriddlyLoader(gdy_path, image_path, shader_path)
self.gdy = loader.load(yaml_file)
if yaml_file is not None:
self.gdy = loader.load(yaml_file)
else:
self.gdy = loader.load_string(yaml_string)

self.game = self.gdy.create_game(global_observer_type)

if max_steps is not None:
Expand Down Expand Up @@ -204,10 +208,29 @@ def render(self, mode='human', observer=0):
observation = np.array(self.game.observe(), copy=False)
if self._global_observer_type == gd.ObserverType.VECTOR:
observation = self._vector2rgb.convert(observation)
if self._global_observer_type == gd.ObserverType.ASCII:
observation = observation \
.swapaxes(2, 0) \
.reshape(-1, observation.shape[0] * observation.shape[1]) \
.view('c')
ascii_string = ''.join(np.column_stack(
(observation, np.repeat(['\n'], observation.shape[0]))
).flatten().tolist())
return ascii_string

else:
observation = self._player_last_observation[observer]
if self._player_observer_type[observer] == gd.ObserverType.VECTOR:
observation = self._vector2rgb.convert(observation)
if self._player_observer_type[observer] == gd.ObserverType.ASCII:
observation = observation \
.swapaxes(2, 0) \
.reshape(-1, observation.shape[0] * observation.shape[1]) \
.view('c')
ascii_string = ''.join(np.column_stack(
(observation, np.repeat(['\n'], observation.shape[0]))
).flatten().tolist())
return ascii_string

if mode == 'human':
if self._renderWindow.get(observer) is None:
Expand Down Expand Up @@ -314,3 +337,17 @@ def build_gym_from_yaml(self, environment_name, yaml_file, global_observer_type=
'player_observer_type': player_observer_type
}
)

def build_gym_from_yaml_string(self, environment_name, yaml_string, global_observer_type=gd.ObserverType.SPRITE_2D,
player_observer_type=gd.ObserverType.SPRITE_2D, level=None, max_steps=None):
register(
id=f'GDY-{environment_name}-v0',
entry_point='griddly:GymWrapper',
kwargs={
'yaml_string': yaml_string,
'level': level,
'max_steps': max_steps,
'global_observer_type': global_observer_type,
'player_observer_type': player_observer_type
}
)
139 changes: 139 additions & 0 deletions python/griddly/util/environment_generator_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os

import gym
import numpy as np
import yaml

from griddly import GymWrapper, gd, GymWrapperFactory
from griddly.RenderTools import VideoRecorder
from griddly.util.wrappers import ValidActionSpaceWrapper


class EnvironmentGeneratorGenerator():

def __init__(self, gdy_path=None, yaml_file=None):
module_path = os.path.dirname(os.path.realpath(__file__))
self._gdy_path = os.path.realpath(
os.path.join(module_path, '../', 'resources', 'games')) if gdy_path is None else gdy_path
self._input_yaml_file = self._get_full_path(yaml_file)

def _get_full_path(self, gdy_path):
# Assume the file is relative first and if not, try to find it in the pre-defined games
fullpath = gdy_path if os.path.exists(gdy_path) else os.path.join(self._gdy_path, gdy_path)
# (for debugging only) look in parent directory resources because we might not have built the latest version
fullpath = fullpath if os.path.exists(fullpath) else os.path.realpath(
os.path.join(self._gdy_path + '../../../../resources/games', gdy_path))
return fullpath

def generate_env_yaml(self, level_shape):
level_generator_gdy = {}
with open(self._input_yaml_file, 'r') as fs:
self._gdy = yaml.load(fs, Loader=yaml.FullLoader)

objects = [o for o in self._gdy['Objects'] if 'MapCharacter' in o]
environment = self._gdy['Environment']

# Create the placement actions
actions = []
for obj in objects:
object_name = obj["Name"]
place_action = {
'InputMapping': {
'Inputs': {
'1': {'Description': f'Places objects of type \"{object_name}\"'}
}
},
'Name': f'place_{object_name.lower()}',
'Behaviours': [{
'Src': {
'Object': '_empty',
'Commands': [
{'spawn': object_name}
]
}
}]

}
actions.append(place_action)

level_generator_gdy['Actions'] = actions

# Copy the Objects
level_generator_gdy['Objects'] = [{
'Name': o['Name'],
'MapCharacter': o['MapCharacter'],
'Observers': o['Observers']
} for o in objects]

# Generate a default empty level
empty_level = np.empty(level_shape, dtype='str')
empty_level[:] = '.'

level_0_string = '\n'.join([' '.join(list(r)) for r in empty_level])

# Create the environment template
level_generator_gdy['Environment'] = {
'Name': f'{environment["Name"]} Generator',
'Description': f'Level Generator environment for {environment["Name"]}',
'Observers': {k: v for k, v in environment['Observers'].items() if k in ['Sprite2D', 'Isometric']},
'Player': {
'Observer': {
'TrackAvatar': False,
'Height': level_shape[1],
'Width': level_shape[0],
'OffsetX': 0,
'OffsetY': 0,
}
},
'Levels': [level_0_string],
}

return yaml.dump(level_generator_gdy)

def generate_env(self, size, **env_kwargs):
env_yaml = self.generate_env_yaml(size)

env_args = {
**env_kwargs,
'yaml_string': env_yaml,
}

return GymWrapper(*env_args)


if __name__ == '__main__':
wrapper_factory = GymWrapperFactory()
yaml_file = 'Single-Player/GVGAI/sokoban.yaml'

egg = EnvironmentGeneratorGenerator(yaml_file=yaml_file)

for i in range(100):
generator_yaml = egg.generate_env_yaml((10, 10))

env_name = f'test_{i}'
wrapper_factory.build_gym_from_yaml_string(
env_name,
yaml_string=generator_yaml,
# TODO: Change this to ASCII observer when its ready
global_observer_type=gd.ObserverType.VECTOR,
player_observer_type=gd.ObserverType.VECTOR,
)

env = gym.make(f'GDY-{env_name}-v0')
env.reset()
#env = ValidActionSpaceWrapper(env)

# visualization = env.render(observer=0, mode='rgb_array')
# video_recorder = VideoRecorder()
# video_recorder.start('generator_video_test.mp4', visualization.shape)

# Place 10 Random Objects
for i in range(0, 100):
action = env.action_space.sample()
obs, reward, done, info = env.step(action)

#state = env.get_state()

#visual = env.render(observer=0, mode='rgb_array')
# video_recorder.add_frame(visual)

4 changes: 2 additions & 2 deletions python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def griddly_package_data(config='Debug'):

setup(
name='griddly',
version="1.2.0",
version="1.2.1",
author_email="chrisbam4d@gmail.com",
description="Griddly Python Libraries",
long_description=long_description,
Expand All @@ -82,7 +82,7 @@ def griddly_package_data(config='Debug'):
install_requires=[
"numpy>=1.20.3",
"gym>=0.17.3",
"pyyaml>-5.3.1",
"pyyaml>=5.3.1",
"imageio>=2.9.0"
],
cmdclass={
Expand Down
Loading

0 comments on commit f9fe6bb

Please sign in to comment.