From 2096e92350c0bf1cd88eb381223894b83b68a582 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 31 Jan 2021 19:18:54 +0000 Subject: [PATCH 01/34] single player support for rllib! --- python/examples/rllib/README.md | 23 ++++++ .../rllib/global_average_pooling_agent.py | 75 +++++++++++++++++++ python/examples/rllib/requirements.txt | 2 + python/examples/rllib/rllib_RTS.py | 0 python/examples/rllib/rllib_multi_agent.py | 68 +++++++++++++++++ python/examples/rllib/rllib_single_agent.py | 43 +++++++++++ python/griddly/util/wrappers/__init__.py | 0 python/griddly/util/wrappers/rllib_wrapper.py | 30 ++++++++ .../valid_action_space_wrapper.py} | 0 9 files changed, 241 insertions(+) create mode 100644 python/examples/rllib/README.md create mode 100644 python/examples/rllib/global_average_pooling_agent.py create mode 100644 python/examples/rllib/requirements.txt create mode 100644 python/examples/rllib/rllib_RTS.py create mode 100644 python/examples/rllib/rllib_multi_agent.py create mode 100644 python/examples/rllib/rllib_single_agent.py create mode 100644 python/griddly/util/wrappers/__init__.py create mode 100644 python/griddly/util/wrappers/rllib_wrapper.py rename python/griddly/util/{wrappers.py => wrappers/valid_action_space_wrapper.py} (100%) diff --git a/python/examples/rllib/README.md b/python/examples/rllib/README.md new file mode 100644 index 000000000..7bb592031 --- /dev/null +++ b/python/examples/rllib/README.md @@ -0,0 +1,23 @@ +# Griddly Rlib Examples + + +## Installation + +Most of the dependencies you need for these examples are already installed, however we've included a `requirements.txt` for extra things that might be required. + +``` +cd examples/rllib +pip install -r requirements.txt +``` + +## Examples + +For full documentation please visit: + +### Single-Player + + +### Multi-Agent + + +### RTS \ No newline at end of file diff --git a/python/examples/rllib/global_average_pooling_agent.py b/python/examples/rllib/global_average_pooling_agent.py new file mode 100644 index 000000000..06f637eef --- /dev/null +++ b/python/examples/rllib/global_average_pooling_agent.py @@ -0,0 +1,75 @@ +from typing import Dict, List + +from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 +from ray.rllib.utils.framework import TensorType +from torch import nn +import numpy as np + + +def layer_init(layer, std=np.sqrt(2), bias_const=0.0): + nn.init.orthogonal_(layer.weight, std) + nn.init.constant_(layer.bias, bias_const) + return layer + + +class GlobalAvePool(nn.Module): + + def __init__(self, final_channels): + super().__init__() + self._final_channels = final_channels + self._pool = nn.Sequential( + nn.AdaptiveAvgPool3d((final_channels, 1, 1)), + nn.Flatten(), + ) + + def forward(self, input): + return self._pool(input) + + +class GAPAgent(TorchModelV2, nn.Module): + """ + Global Average Pooling Agent + """ + + def __init__(self, obs_space, action_space, num_outputs, model_config, name): + super().__init__(obs_space, action_space, num_outputs, model_config, name) + nn.Module.__init__(self) + + self._num_objects = obs_space.shape[2] + self._num_actions = num_outputs + + self.network = nn.Sequential( + layer_init(nn.Conv2d(self._num_objects, 32, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(32, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + GlobalAvePool(2048), + layer_init(nn.Linear(2048, 512)), + nn.ReLU(), + layer_init(nn.Linear(512, 512)) + ) + + self._actor_head = nn.Sequential( + layer_init(nn.Linear(512, 512), std=0.01), + nn.ReLU(), + layer_init(nn.Linear(512, self._num_actions), std=0.01) + ) + + self._critic_head = nn.Sequential( + layer_init(nn.Linear(512, 1), std=0.01) + ) + + def forward(self, input_dict, state, seq_lens): + obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) + network_output = self.network(obs_transformed) + value = self._critic_head(network_output) + self._value = value.reshape(-1) + logits = self._actor_head(network_output) + return logits, state + + def value_function(self): + return self._value diff --git a/python/examples/rllib/requirements.txt b/python/examples/rllib/requirements.txt new file mode 100644 index 000000000..ee2962b46 --- /dev/null +++ b/python/examples/rllib/requirements.txt @@ -0,0 +1,2 @@ +ray[rllib] >= 1.1.0 +torch >= 1.7.1 \ No newline at end of file diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/examples/rllib/rllib_multi_agent.py b/python/examples/rllib/rllib_multi_agent.py new file mode 100644 index 000000000..2405c4a46 --- /dev/null +++ b/python/examples/rllib/rllib_multi_agent.py @@ -0,0 +1,68 @@ +import gym +from gym.wrappers import TransformObservation +from ray.rllib.agents.impala import ImpalaTrainer + +from griddly import gd, GymWrapper +import ray +from ray.tune.registry import register_env + +env_name = "ray-griddly-env" + +def env_creator(env_config): + env = GymWrapper(**env_config) + env.reset() + print(env.observation_space.shape) + env.observation_space = gym.spaces.Box( + env.observation_space.low.transpose((1, 2, 0)), + env.observation_space.high.transpose((1, 2, 0)) + ) + env = TransformObservation(env, lambda obs: obs.transpose(1, 2, 0)) + print(env.observation_space.shape) + return env + + +register_env(env_name, env_creator) + +if __name__ == '__main__': + + ray.init(num_gpus=2) + rllib_config = { + "framework": "torch", + "num_workers": 12, + "num_envs_per_worker": 3, + "model": { + # "conv_filters": [[32, (13, 9), 1]], + "conv_filters": [ + [64, (3, 3), 1], + [32, (3, 3), 1], + [32, (28,11),1] + # [32, (3, 3), 1] + ], + # 'fcnet_hiddens': [256, 128, 64], + # 'use_lstm': True, + # 'max_seq_len': 32 + }, + "env_config": { + 'yaml_file': 'Single-Player/GVGAI/butterflies.yaml', + "level": 6, + "max_steps": 1000, + }, + + } + + trainer = ImpalaTrainer(rllib_config, env=env_name) + + log_string = "{:3d} reward {:6.2f}/{:6.2f}/{:6.2f} len {:6.2f}. Total Trained: {:6.2f}" + + for s in range(10000): + result = trainer.train() + print( + log_string.format( + s + 1, + result["episode_reward_min"], + result["episode_reward_mean"], + result["episode_reward_max"], + result["episode_len_mean"], + result["info"]["num_steps_trained"], + ) + ) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py new file mode 100644 index 000000000..0dfe7a256 --- /dev/null +++ b/python/examples/rllib/rllib_single_agent.py @@ -0,0 +1,43 @@ +import ray +from ray.rllib.agents.impala import ImpalaTrainer +from ray.tune.registry import register_env +from ray.rllib.models import ModelCatalog + +from examples.rllib.global_average_pooling_agent import GAPAgent +from griddly import gd +from griddly.util.wrappers.rllib_wrapper import RLlibWrapper + +env_name = "ray-griddly-env" + +register_env(env_name, RLlibWrapper) +ModelCatalog.register_custom_model("GAP", GAPAgent) + +if __name__ == '__main__': + + ray.init() + rllib_config = { + 'framework': 'torch', + 'num_workers': 12, + 'num_envs_per_worker': 3, + 'monitor': True, + 'model': { + 'custom_model': 'GAP', + 'custom_model_config': {} + }, + 'env_config': { + 'yaml_file': 'Single-Player/GVGAI/random_butterflies.yaml', + 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'level': 6, + 'max_steps': 1000, + } + } + + trainer = ImpalaTrainer(rllib_config, env=env_name) + + for s in range(10000): + result = trainer.train() + print( + f'Reward (min/mean/max): {result["episode_reward_min"]:.2f}/{result["episode_reward_mean"]:.2f}/{result["episode_reward_max"]:.2f}. ' + f'Mean Episode Length: {result["episode_len_mean"]:.2f}. ' + f'Total Trained Steps: {result["info"]["num_steps_trained"]}' + ) diff --git a/python/griddly/util/wrappers/__init__.py b/python/griddly/util/wrappers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/wrappers/rllib_wrapper.py b/python/griddly/util/wrappers/rllib_wrapper.py new file mode 100644 index 000000000..e2248f66d --- /dev/null +++ b/python/griddly/util/wrappers/rllib_wrapper.py @@ -0,0 +1,30 @@ +import gym + +from griddly import GymWrapper + + +class RLibWrapper(GymWrapper): + + def __init__(self, env_config): + super().__init__(**env_config) + + print(env.observation_space.shape) + env.observation_space = gym.spaces.Box( + env.observation_space.low.transpose((1, 2, 0)), + env.observation_space.high.transpose((1, 2, 0)) + ) + env = TransformObservation(env, lambda obs: obs.transpose(1, 2, 0)) + print(env.observation_space.shape) + + reset() + + def reset(self, **kwargs): + observation = self.env.reset(**kwargs) + return self.transform(observation) + + def step(self, action): + observation, reward, done, info = self.env.step(action) + return self.transform(observation), reward, done, info + + def observation(self, observation): + raise NotImplementedError \ No newline at end of file diff --git a/python/griddly/util/wrappers.py b/python/griddly/util/wrappers/valid_action_space_wrapper.py similarity index 100% rename from python/griddly/util/wrappers.py rename to python/griddly/util/wrappers/valid_action_space_wrapper.py From d729f2e0f44ef4311cfa8759e408a0d9a55bf8ed Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Feb 2021 20:14:41 +0000 Subject: [PATCH 02/34] adding bits and peices for compatibility with action masking --- bindings/python.cpp | 3 + bindings/wrapper/GameWrapper.cpp | 15 +- .../griddlyrts/griddly_rts_global.png | Bin 895 -> 459 bytes python/examples/griddlyrts/griddly_rts_p1.png | Bin 895 -> 459 bytes python/examples/griddlyrts/griddly_rts_p2.png | Bin 895 -> 459 bytes .../griddlyrts/play_griddlyrts_gym.py | 2 +- python/examples/nmmo.yaml | 282 ------------------ python/examples/rllib/rllib_RTS.py | 58 ++++ python/examples/rllib/rllib_multi_agent.py | 68 ----- python/examples/rllib/rllib_single_agent.py | 46 +-- python/examples/rllib_experiment.py | 94 ------ python/griddly/util/rllib/__init__.py | 1 + python/griddly/util/rllib/torch/__init__.py | 1 + .../torch}/global_average_pooling_agent.py | 12 +- .../rllib/torch/invalid_action_exploration.py | 24 ++ python/griddly/util/rllib/wrappers.py | 202 +++++++++++++ python/griddly/util/wrappers/__init__.py | 1 + python/griddly/util/wrappers/rllib_wrapper.py | 30 -- python/setup.py | 2 +- src/Griddly/Core/GameProcess.cpp | 2 +- src/Griddly/Core/GameProcess.hpp | 2 +- src/Griddly/Core/Grid.cpp | 2 +- src/Griddly/Core/Grid.hpp | 2 +- 23 files changed, 342 insertions(+), 507 deletions(-) delete mode 100644 python/examples/nmmo.yaml delete mode 100644 python/examples/rllib/rllib_multi_agent.py delete mode 100644 python/examples/rllib_experiment.py create mode 100644 python/griddly/util/rllib/__init__.py create mode 100644 python/griddly/util/rllib/torch/__init__.py rename python/{examples/rllib => griddly/util/rllib/torch}/global_average_pooling_agent.py (86%) create mode 100644 python/griddly/util/rllib/torch/invalid_action_exploration.py create mode 100644 python/griddly/util/rllib/wrappers.py delete mode 100644 python/griddly/util/wrappers/rllib_wrapper.py diff --git a/bindings/python.cpp b/bindings/python.cpp index d662770bf..d82e4b133 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -78,6 +78,9 @@ PYBIND11_MODULE(python_griddly, m) { // Get a dictionary containing the objects in the environment and their variable values game_process.def("get_state", &Py_GameWrapper::getState); + // Get a specific variable value + game_process.def("get_global_variable", &Py_GameWrapper::getGlobalVariable); + // Get a list of the events that have happened in the game up to this point game_process.def("get_history", &Py_GameWrapper::getHistory, py::arg("purge")=true); diff --git a/bindings/wrapper/GameWrapper.cpp b/bindings/wrapper/GameWrapper.cpp index 8d58b4eec..f64f0a0b9 100644 --- a/bindings/wrapper/GameWrapper.cpp +++ b/bindings/wrapper/GameWrapper.cpp @@ -217,7 +217,7 @@ class Py_GameWrapper { py::dict getState() const { py::dict py_state; - auto state = gameProcess_->getState(); + auto& state = gameProcess_->getState(); py_state["GameTicks"] = state.gameTicks; @@ -252,6 +252,19 @@ class Py_GameWrapper { return py_state; } + py::dict getGlobalVariables(std::vector variables) const { + + py::dict py_globalVariables; + auto globalVariables = gameProcess_->getGrid()->getGlobalVariables(); + + for (auto variableNameIt : variables) { + + state.globalVariables['variableNameIt'] + py_globalVariables[varIt.first.c_str()] = varIt.second; + } + + } + std::vector getHistory(bool purge) const { auto history = gameProcess_->getGrid()->getHistory(); diff --git a/python/examples/griddlyrts/griddly_rts_global.png b/python/examples/griddlyrts/griddly_rts_global.png index 92dffddaf8577bafab91b14df23ca3aa09c5e138..65c71f90b3d5c9bcd2ecb8a3b84a2524de085eb6 100644 GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^zkoP}g9%6~JlYk>z`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXz`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXz`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIX 0, but we are still missing python_griddly after this - - # wrapper = GymWrapperFactory() - # wrapper.build_gym_from_yaml( - # 'Spider-Nest', - # 'Single-Player/GVGAI/spider-nest.yaml', - # player_observer_type=gd.ObserverType.VECTOR, - # level=0 - # ) - - def reset(self): - obs = self.env.reset() - obs = obs.transpose(reshp) - - return obs - - def step(self, act): - obs, rew, done, info = self.env.step(act) - obs = obs.transpose(reshp) - # self.render() - - return obs, rew, done, info - - def render(self): - self.env.render(observer="global") - - -if __name__ == '__main__': - unregister() - wrapper = GymWrapperFactory() - wrapper.build_gym_from_yaml( - 'Spider-Nest', - 'Single-Player/GVGAI/spider-nest.yaml', - player_observer_type=gd.ObserverType.VECTOR, - level=0 - ) - ray.init() - rllib_config = { - "framework": "torch", - "num_workers": 0, - "num_envs_per_worker": 1, - "model": { - # "conv_filters": [[32, (13, 9), 1]], - "conv_filters": [[32, (7, 7), 1]], - }, - "env_config": { - "config": None, - }, - - } - - trainer = ppo.PPOTrainer(config=rllib_config, env=RLLibEnv) - - TEST = False - - if not TEST: - while True: - res = trainer.train() - print(res) - - else: - env = RLLibEnv() - env.reset() - - for i in range(1000): - env.step(env.action_space.sample()) - env.render() \ No newline at end of file diff --git a/python/griddly/util/rllib/__init__.py b/python/griddly/util/rllib/__init__.py new file mode 100644 index 000000000..ba1db8067 --- /dev/null +++ b/python/griddly/util/rllib/__init__.py @@ -0,0 +1 @@ +from griddly.util.rllib.wrappers import RLlibWrapper, RLlibMultiAgentWrapper \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/__init__.py b/python/griddly/util/rllib/torch/__init__.py new file mode 100644 index 000000000..5aa3c5d5b --- /dev/null +++ b/python/griddly/util/rllib/torch/__init__.py @@ -0,0 +1 @@ +from griddly.util.rllib.torch.global_average_pooling_agent import GAPAgent \ No newline at end of file diff --git a/python/examples/rllib/global_average_pooling_agent.py b/python/griddly/util/rllib/torch/global_average_pooling_agent.py similarity index 86% rename from python/examples/rllib/global_average_pooling_agent.py rename to python/griddly/util/rllib/torch/global_average_pooling_agent.py index 06f637eef..226ab3ef8 100644 --- a/python/examples/rllib/global_average_pooling_agent.py +++ b/python/griddly/util/rllib/torch/global_average_pooling_agent.py @@ -1,9 +1,6 @@ -from typing import Dict, List - +import numpy as np from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 -from ray.rllib.utils.framework import TensorType from torch import nn -import numpy as np def layer_init(layer, std=np.sqrt(2), bias_const=0.0): @@ -29,13 +26,16 @@ def forward(self, input): class GAPAgent(TorchModelV2, nn.Module): """ Global Average Pooling Agent + This is the same agent used in https://arxiv.org/abs/2011.06363. + + Global average pooling is a simple way to allow training grid-world environments regardless o the size of the grid. """ def __init__(self, obs_space, action_space, num_outputs, model_config, name): super().__init__(obs_space, action_space, num_outputs, model_config, name) nn.Module.__init__(self) - self._num_objects = obs_space.shape[2] + self._num_objects = obs_space.original_space['obs'].shape[2] self._num_actions = num_outputs self.network = nn.Sequential( @@ -64,7 +64,7 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): ) def forward(self, input_dict, state, seq_lens): - obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) + obs_transformed = input_dict['obs']['obs'].permute(0, 3, 1, 2) network_output = self.network(obs_transformed) value = self._critic_head(network_output) self._value = value.reshape(-1) diff --git a/python/griddly/util/rllib/torch/invalid_action_exploration.py b/python/griddly/util/rllib/torch/invalid_action_exploration.py new file mode 100644 index 000000000..bf2af2b4e --- /dev/null +++ b/python/griddly/util/rllib/torch/invalid_action_exploration.py @@ -0,0 +1,24 @@ +from typing import Union + +from ray.rllib.models import ActionDistribution +from ray.rllib.utils.exploration import StochasticSampling +from ray.rllib.utils.framework import TensorType + + +class InvalidActionMaskingPolicyWrapper(): + + @staticmethod + def action_sampler_fn(policy, model, input_dict, state_out, explore, timestep): + + location_mask = input_dict['location_action_mask'] + action_masks = input_dict['unit_action_mask'] + + # Firstly sample from the location mask + + + # Use the sampled location to retrieve the unit action mask + + + @staticmethod + def wrap(policy_cls, name): + return policy_cls.with_updates(name, action_sampler_fn=InvalidActionMaskingPolicyWrapper.action_sampler_fn) diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py new file mode 100644 index 000000000..6bdf7ad10 --- /dev/null +++ b/python/griddly/util/rllib/wrappers.py @@ -0,0 +1,202 @@ +from typing import Tuple + +import gym +from gym.spaces import Dict +from ray.rllib import MultiAgentEnv +from ray.rllib.utils.typing import MultiAgentDict + +from griddly import GymWrapper +import numpy as np + + +class ValidActionTreeSpace(gym.spaces.Space): + """ + Allows possible actions to be passed to the policy as an observation so it can be used in invalid action masking + """ + + def __init__(self, shape=None, dtype=None): + super().__init__(shape, dtype) + + def sample(self): + return np.zeros(self.shape) + + def contains(self, x): + return len(x.shape) == len(self.shape) and \ + x.shape[0] <= self.shape[0] and \ + x.shape[1] == self.shape[1] + + +class RLlibWrapper(GymWrapper): + """ + Wraps a Griddly environment for compatibility with RLLib. + + Use the `env_config` in the rllib config to provide Griddly Environment Parameters + + Example: + + Firstly register the RLlibWrapper using rllib's + + env_name = "my_env_name" + + register_env(env_name, RLlibWrapper) + + you can then configure it + + rllib_config = { + 'env_config': { + 'yaml_file': 'Single-Player/GVGAI/butterflies.yaml', + 'level": 6, + 'player_observer_type': gd.ObserverType.SPRITE_2D, + 'global_observer_type': gd.ObserverType.ISOMETRIC, + 'max_steps': 1000, + }, + # Other configuration options + } + + Create the rllib trainer using this config: + + trainer = ImpalaTrainer(rllib_config, env=env_name) + + """ + + def __init__(self, env_config): + super().__init__(**env_config) + + self._invalid_action_masking = env_config.get('invalid_action_masking', False) + + super().reset() + + self.set_transform() + + def _transform_obs_space(self, observation_space): + + obs_space_dict = { + 'obs': gym.spaces.Box( + observation_space.low.transpose((1, 2, 0)), + observation_space.high.transpose((1, 2, 0)), + dtype=np.float, + ) + } + + # To fit and compress the valid action data + if self._invalid_action_masking: + # TODO: valid action tree almost certainly can be compressed in a better format than it is currently. + # RLLib forces these variables to be Tensors, so this will have to do for now + + # Basically contain a list of [x,y,action_type,action_id_mask] + shape = ( + self.grid_width * self.grid_height * self.action_count, + 3 + self.max_action_ids # x,y,action_type,action_id_mask + ) + + obs_space_dict['valid_action_tree'] = ValidActionTreeSpace(shape=shape) + + return Dict(obs_space_dict) + + def _get_location_mask_for_player(self, player_id): + if not self.has_avatar: + grid_width_mask = np.zeros(self.grid_width) + grid_height_mask = np.zeros(self.grid_height) + + locations_and_action_names = self.game.get_available_actions(player_id) + for location, action_names in locations_and_action_names.items(): + grid_width_mask[location[0]] = 1 + grid_height_mask[location[1]] = 1 + return grid_height_mask, grid_width_mask, locations_and_action_names + + def _transform_with_action_mask(self, observation): + pass + # return_obs = {} + # + # if self.player_count > 0: + # + # for p in self.player_count: + # grid_height_mask, grid_width_mask, locations_and_action_names = self._get_location_mask_for_player(p+1) + # return_obs['location_action_mask'] = np.concat([grid_height_mask, grid_width_mask]) + # + # for location, action_names in locations_and_action_names.items(): + # + # if self.action_count > 1: + # + # unit_action_mask_parts = [] + # if self.action_count > 1: + # unit_action_mask_parts.append(self.action_count) + # + # unit_action_mask_parts.append(self.max_action_ids) + # + # obs_space_dict['unit_action_mask'] = gym.spaces.Dict(0, 1, shape=unit_action_mask_parts, dtype=np.float) + + def _obs_transform(self, obs): + return obs.transpose(1, 2, 0).astype(np.float) + + def _transform(self, observation): + + transformed_obs = {} + + if self.player_count > 1: + transformed_obs['obs'] = [self._obs_transform(obs) for obs in observation['obs']] + else: + transformed_obs['obs'] = self._obs_transform(observation['obs']) + + if self._invalid_action_masking: + transformed_obs['valid_action_tree'] = + + def set_transform(self): + """ + Create the transform for rllib based on the observation space + """ + + if self.player_count > 1: + self.observation_space = self.observation_space[0] + self.action_space = self.action_space[0] + + self.observation_space = self._transform_obs_space(self.observation_space) + + def reset(self, **kwargs): + observation = super().reset(**kwargs) + self.set_transform() + return self._transform(observation) + + def step(self, action): + observation, reward, done, info = super().step(action) + return self._transform(observation), reward, done, info + + def render(self, mode='human', observer=0): + return super().render(mode, observer='global') + + +class RLlibMultiAgentWrapper(RLlibWrapper, MultiAgentEnv): + + def __init__(self, env_config): + super().__init__(**env_config) + + self._player_done_variable = env_config.get('player_done_variable', None) + + assert self.player_count > 1, 'RLlibMultiAgentWrapper can only be used with environments that have multiple agents' + + def _to_rlib_obs(self, obs_array): + return {p: obs for p, obs in enumerate(obs_array)} + + def reset(self, **kwargs): + obs = super().reset(**kwargs) + return self._to_rlib_obs(obs) + + def get_info(self): + return self.info + + def step(self, action_dict: MultiAgentDict): + actions_array = np.zeros((self.player_count, *self.action_space.shape)) + for agent_id, action in action_dict.items(): + actions_array[agent_id] = action + + obs, reward, all_done, self.info = super().step(actions_array) + + done = {'__all__': all_done} + + if self._player_done_variable is not None: + player_done = self.game.get_global_variable([self._player_done_variable]) + else: + for p in range(self.player_count): + done[p + 1] = False + + return self._to_rlib_obs(obs), self._to_rlib_obs(reward), done, {} diff --git a/python/griddly/util/wrappers/__init__.py b/python/griddly/util/wrappers/__init__.py index e69de29bb..2da6ac188 100644 --- a/python/griddly/util/wrappers/__init__.py +++ b/python/griddly/util/wrappers/__init__.py @@ -0,0 +1 @@ +from griddly.util.wrappers.valid_action_space_wrapper import ValidActionSpaceWrapper \ No newline at end of file diff --git a/python/griddly/util/wrappers/rllib_wrapper.py b/python/griddly/util/wrappers/rllib_wrapper.py deleted file mode 100644 index e2248f66d..000000000 --- a/python/griddly/util/wrappers/rllib_wrapper.py +++ /dev/null @@ -1,30 +0,0 @@ -import gym - -from griddly import GymWrapper - - -class RLibWrapper(GymWrapper): - - def __init__(self, env_config): - super().__init__(**env_config) - - print(env.observation_space.shape) - env.observation_space = gym.spaces.Box( - env.observation_space.low.transpose((1, 2, 0)), - env.observation_space.high.transpose((1, 2, 0)) - ) - env = TransformObservation(env, lambda obs: obs.transpose(1, 2, 0)) - print(env.observation_space.shape) - - reset() - - def reset(self, **kwargs): - observation = self.env.reset(**kwargs) - return self.transform(observation) - - def step(self, action): - observation, reward, done, info = self.env.step(action) - return self.transform(observation), reward, done, info - - def observation(self, observation): - raise NotImplementedError \ No newline at end of file diff --git a/python/setup.py b/python/setup.py index c9904a117..c42d1768c 100644 --- a/python/setup.py +++ b/python/setup.py @@ -79,7 +79,7 @@ def griddly_package_data(config='Debug'): url="https://github.com/bam4d/Griddly", package_data={'griddly': griddly_package_data('Release')}, - packages=['griddly', 'griddly.util'], + packages=['griddly', 'griddly.util', 'griddly.util.wrappers', 'griddly.util.rllib', 'griddly.util.rllib.torch'], install_requires=[ "numpy>=1.19.1", "gym>=0.17.2", diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index 8b642c16b..5d6c2be82 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -267,7 +267,7 @@ std::vector GameProcess::getAvailableActionIdsAtLocation(glm::ivec2 lo return availableActionIds; } -StateInfo GameProcess::getState() const { +StateInfo& GameProcess::getState() const { StateInfo stateInfo; stateInfo.gameTicks = *grid_->getTickCount(); diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index fc347d95f..6ee749af6 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -63,7 +63,7 @@ class GameProcess : public std::enable_shared_from_this { virtual std::vector getAvailableActionIdsAtLocation(glm::ivec2 location, std::string actionName) const; - virtual StateInfo getState() const; + virtual StateInfo& getState() const; virtual uint32_t getNumPlayers() const; diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 83320e715..cc6866e57 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -318,7 +318,7 @@ std::unordered_map> Grid::getObjectCounter(st return objectCounterIt->second; } -std::unordered_map>> Grid::getGlobalVariables() const { +const std::unordered_map>>& Grid::getGlobalVariables() const { return globalVariables_; } diff --git a/src/Griddly/Core/Grid.hpp b/src/Griddly/Core/Grid.hpp index 293c0dbb3..334bd08c7 100644 --- a/src/Griddly/Core/Grid.hpp +++ b/src/Griddly/Core/Grid.hpp @@ -105,7 +105,7 @@ class Grid : public std::enable_shared_from_this { virtual std::unordered_map> getObjectCounter(std::string objectName); - virtual std::unordered_map>> getGlobalVariables() const; + virtual const std::unordered_map>>& getGlobalVariables() const; virtual void enableHistory(bool enable); virtual std::vector getHistory() const; From 49c1ba3365c1d9585c36ab6ea070a36a323e1edb Mon Sep 17 00:00:00 2001 From: Bam4d Date: Fri, 5 Feb 2021 17:55:31 +0000 Subject: [PATCH 03/34] finally figured out how to get dictionary data to the action selection part of rllib code --- bindings/python.cpp | 2 +- bindings/wrapper/GameWrapper.cpp | 12 +- python/examples/rllib/rllib_RTS.py | 46 +++++-- python/examples/rllib/rllib_single_agent.py | 3 + python/griddly/GymWrapper.py | 2 +- .../torch/global_average_pooling_agent.py | 8 +- python/griddly/util/rllib/torch/mixins.py | 61 +++++++++ python/griddly/util/rllib/wrappers.py | 127 ++++++------------ 8 files changed, 154 insertions(+), 107 deletions(-) create mode 100644 python/griddly/util/rllib/torch/mixins.py diff --git a/bindings/python.cpp b/bindings/python.cpp index d82e4b133..c4f5b7215 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -79,7 +79,7 @@ PYBIND11_MODULE(python_griddly, m) { game_process.def("get_state", &Py_GameWrapper::getState); // Get a specific variable value - game_process.def("get_global_variable", &Py_GameWrapper::getGlobalVariable); + game_process.def("get_global_variable", &Py_GameWrapper::getGlobalVariables); // Get a list of the events that have happened in the game up to this point game_process.def("get_history", &Py_GameWrapper::getHistory, py::arg("purge")=true); diff --git a/bindings/wrapper/GameWrapper.cpp b/bindings/wrapper/GameWrapper.cpp index f64f0a0b9..55cd3bb31 100644 --- a/bindings/wrapper/GameWrapper.cpp +++ b/bindings/wrapper/GameWrapper.cpp @@ -258,11 +258,17 @@ class Py_GameWrapper { auto globalVariables = gameProcess_->getGrid()->getGlobalVariables(); for (auto variableNameIt : variables) { + std::unordered_map resolvedGlobalVariableMap; - state.globalVariables['variableNameIt'] - py_globalVariables[varIt.first.c_str()] = varIt.second; - } + auto globalVariableMap = globalVariables[variableNameIt]; + for(auto playerVariableIt : globalVariableMap) { + resolvedGlobalVariableMap.insert({playerVariableIt.first, *playerVariableIt.second}); + } + + py_globalVariables[variableNameIt.c_str()] = resolvedGlobalVariableMap; + } + return py_globalVariables; } std::vector getHistory(bool purge) const { diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index 4d65a429c..6fd29070d 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -4,35 +4,59 @@ import ray from ray import tune from ray.rllib.agents.impala import ImpalaTrainer -from ray.rllib.agents.impala.vtrace_tf_policy import VTraceTFPolicy -from ray.rllib.models import ModelCatalog, ActionDistribution +from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, setup_mixins +from ray.rllib.models import ModelCatalog +from ray.rllib.policy.torch_policy import LearningRateSchedule, EntropyCoeffSchedule from ray.tune.registry import register_env from griddly import gd from griddly.util.rllib import RLlibMultiAgentWrapper from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.torch.mixins import InvalidActionMaskingPolicyMixin +def setup_invalid_mask_mixin(policy, obs_space, action_space, config): + InvalidActionMaskingPolicyMixin.__init__(policy) + setup_mixins(policy, obs_space, action_space, config) + if __name__ == '__main__': sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(local_mode=True) + ray.init(num_gpus=1) env_name = 'ray-griddly-rts-env' register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) - #ActionDistribution.with_updates() - InvalidActionMaskingPolicyWrapper(VTraceTFPolicy, 'InvalidActionMaskingVTrace') + InvalidActionMaskingTorchPolicy = VTraceTorchPolicy.with_updates( + name='InvalidActionMaskingTorchPolicy', + before_init=setup_invalid_mask_mixin, + mixins=[ + LearningRateSchedule, + EntropyCoeffSchedule, + InvalidActionMaskingPolicyMixin + ]) + + + def get_policy_class(config): + if config['framework'] == 'torch': + return InvalidActionMaskingTorchPolicy + + + InvalidActionMaskingImpalaTrainer = ImpalaTrainer.with_updates(default_policy=InvalidActionMaskingTorchPolicy, + get_policy_class=get_policy_class) config = { 'framework': 'torch', - 'num_workers': 1, - 'num_envs_per_worker': 1, - #'monitor': True, + 'num_workers': 8, + 'num_envs_per_worker': 4, + + # Must be set to false to use the InvalidActionMaskingPolicyMixin + "_use_trajectory_view_api": False, + 'monitor': True, 'model': { 'custom_model': 'GAP', 'custom_model_config': {} @@ -40,10 +64,10 @@ 'env': env_name, 'env_config': { # Tell the RLlib wrapper to use invalid action masking - #'invalid_action_masking': True, + 'invalid_action_masking': True, 'yaml_file': 'RTS/Stratega/heal-or-die.yaml', - #'global_observer_type': gd.ObserverType.SPRITE_2D, + 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 0, 'max_steps': 1000, }, @@ -55,4 +79,4 @@ 'timesteps_total': 1000000, } - result = tune.run(ImpalaTrainer, config=config, stop=stop) + result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index e9a0324b7..a6f90196f 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -33,6 +33,9 @@ }, 'env': env_name, 'env_config': { + # Uncomment this line to apply invalid action masking + #'invalid_action_masking': True, + 'yaml_file': 'Single-Player/GVGAI/random_butterflies.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 6, diff --git a/python/griddly/GymWrapper.py b/python/griddly/GymWrapper.py index 8ad2ca728..a16fc1cb9 100644 --- a/python/griddly/GymWrapper.py +++ b/python/griddly/GymWrapper.py @@ -13,7 +13,7 @@ class GymWrapper(gym.Env): def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType.VECTOR, player_observer_type=gd.ObserverType.VECTOR, max_steps=None, image_path=None, shader_path=None, - gdy=None, game=None): + gdy=None, game=None, **kwargs): """ Currently only supporting a single player (player 1 as defined in the environment yaml :param yaml_file: diff --git a/python/griddly/util/rllib/torch/global_average_pooling_agent.py b/python/griddly/util/rllib/torch/global_average_pooling_agent.py index 226ab3ef8..4dcdf179a 100644 --- a/python/griddly/util/rllib/torch/global_average_pooling_agent.py +++ b/python/griddly/util/rllib/torch/global_average_pooling_agent.py @@ -35,7 +35,7 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): super().__init__(obs_space, action_space, num_outputs, model_config, name) nn.Module.__init__(self) - self._num_objects = obs_space.original_space['obs'].shape[2] + self._num_objects = obs_space.shape[2] self._num_actions = num_outputs self.network = nn.Sequential( @@ -63,8 +63,12 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): layer_init(nn.Linear(512, 1), std=0.01) ) + self.view_requirements = { + + } + def forward(self, input_dict, state, seq_lens): - obs_transformed = input_dict['obs']['obs'].permute(0, 3, 1, 2) + obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) network_output = self.network(obs_transformed) value = self._critic_head(network_output) self._value = value.reshape(-1) diff --git a/python/griddly/util/rllib/torch/mixins.py b/python/griddly/util/rllib/torch/mixins.py new file mode 100644 index 000000000..24a3a500c --- /dev/null +++ b/python/griddly/util/rllib/torch/mixins.py @@ -0,0 +1,61 @@ +from typing import Callable, Dict, List, Optional, Tuple, Type, Union + +from ray.rllib import SampleBatch, Policy +from ray.rllib.utils import override +from ray.rllib.utils.torch_ops import convert_to_torch_tensor +from ray.rllib.utils.typing import TensorType + +import numpy as np +import torch + + +class InvalidActionMaskingPolicyMixin: + + @override(Policy) + def compute_actions( + self, + obs_batch: Union[List[TensorType], TensorType], + state_batches: Optional[List[TensorType]] = None, + prev_action_batch: Union[List[TensorType], TensorType] = None, + prev_reward_batch: Union[List[TensorType], TensorType] = None, + info_batch: Optional[Dict[str, list]] = None, + episodes: Optional[List["MultiAgentEpisode"]] = None, + explore: Optional[bool] = None, + timestep: Optional[int] = None, + **kwargs) -> \ + Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: + + explore = explore if explore is not None else self.config["explore"] + timestep = timestep if timestep is not None else self.global_timestep + + with torch.no_grad(): + seq_lens = torch.ones(len(obs_batch), dtype=torch.int32) + input_dict = self._lazy_tensor_dict({ + SampleBatch.CUR_OBS: np.asarray(obs_batch), + "is_training": False, + }) + if prev_action_batch is not None: + input_dict[SampleBatch.PREV_ACTIONS] = \ + np.asarray(prev_action_batch) + if prev_reward_batch is not None: + input_dict[SampleBatch.PREV_REWARDS] = \ + np.asarray(prev_reward_batch) + state_batches = [ + convert_to_torch_tensor(s, self.device) + for s in (state_batches or []) + ] + + return self._compute_action_helper(input_dict, state_batches, + seq_lens, explore, timestep) + + # @override(Policy) + # def compute_actions_from_input_dict( + # self, + # input_dict: Dict[str, TensorType], + # explore: bool = None, + # timestep: Optional[int] = None, + # episodes: Optional[List["MultiAgentEpisode"]] = None, + # **kwargs) -> \ + # Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: + # raise NotImplementedError('Invalid action masking cannot be used with Trajectory View API. please set ' + # '"_use_trajectory_view_api": False, in the training config.') diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index 6bdf7ad10..368c7fc13 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -1,3 +1,4 @@ +from collections import defaultdict from typing import Tuple import gym @@ -9,23 +10,6 @@ import numpy as np -class ValidActionTreeSpace(gym.spaces.Space): - """ - Allows possible actions to be passed to the policy as an observation so it can be used in invalid action masking - """ - - def __init__(self, shape=None, dtype=None): - super().__init__(shape, dtype) - - def sample(self): - return np.zeros(self.shape) - - def contains(self, x): - return len(x.shape) == len(self.shape) and \ - x.shape[0] <= self.shape[0] and \ - x.shape[1] == self.shape[1] - - class RLlibWrapper(GymWrapper): """ Wraps a Griddly environment for compatibility with RLLib. @@ -70,76 +54,40 @@ def __init__(self, env_config): def _transform_obs_space(self, observation_space): - obs_space_dict = { - 'obs': gym.spaces.Box( - observation_space.low.transpose((1, 2, 0)), - observation_space.high.transpose((1, 2, 0)), - dtype=np.float, - ) - } + return gym.spaces.Box( + observation_space.low.transpose((1, 2, 0)), + observation_space.high.transpose((1, 2, 0)), + dtype=np.float, + ) - # To fit and compress the valid action data - if self._invalid_action_masking: - # TODO: valid action tree almost certainly can be compressed in a better format than it is currently. - # RLLib forces these variables to be Tensors, so this will have to do for now - - # Basically contain a list of [x,y,action_type,action_id_mask] - shape = ( - self.grid_width * self.grid_height * self.action_count, - 3 + self.max_action_ids # x,y,action_type,action_id_mask - ) - - obs_space_dict['valid_action_tree'] = ValidActionTreeSpace(shape=shape) - - return Dict(obs_space_dict) - - def _get_location_mask_for_player(self, player_id): - if not self.has_avatar: - grid_width_mask = np.zeros(self.grid_width) - grid_height_mask = np.zeros(self.grid_height) - - locations_and_action_names = self.game.get_available_actions(player_id) - for location, action_names in locations_and_action_names.items(): - grid_width_mask[location[0]] = 1 - grid_height_mask[location[1]] = 1 - return grid_height_mask, grid_width_mask, locations_and_action_names - - def _transform_with_action_mask(self, observation): - pass - # return_obs = {} - # - # if self.player_count > 0: - # - # for p in self.player_count: - # grid_height_mask, grid_width_mask, locations_and_action_names = self._get_location_mask_for_player(p+1) - # return_obs['location_action_mask'] = np.concat([grid_height_mask, grid_width_mask]) - # - # for location, action_names in locations_and_action_names.items(): - # - # if self.action_count > 1: - # - # unit_action_mask_parts = [] - # if self.action_count > 1: - # unit_action_mask_parts.append(self.action_count) - # - # unit_action_mask_parts.append(self.max_action_ids) - # - # obs_space_dict['unit_action_mask'] = gym.spaces.Dict(0, 1, shape=unit_action_mask_parts, dtype=np.float) - - def _obs_transform(self, obs): - return obs.transpose(1, 2, 0).astype(np.float) + def _get_player_action_tree(self, player_id): - def _transform(self, observation): + valid_action_tree = defaultdict(lambda: defaultdict(lambda: defaultdict(defaultdict))) + for location, action_names in self.game.get_available_actions(player_id).items(): + for action_name, action_ids in self.game.get_available_action_ids(location, list(action_names)).items(): + valid_action_tree[location[0]][location[1]][self.action_names.index(action_name)] = action_ids + return valid_action_tree - transformed_obs = {} + def _build_valid_action_trees(self): + player_valid_action_trees = [] + + if self.player_count > 0: + for p in range(self.player_count): + player_valid_action_trees.append({'valid_action_tree':self._get_player_action_tree(p + 1)}) + + else: + player_valid_action_trees.append({'valid_action_tree': self._get_player_action_tree(1)}) + + return player_valid_action_trees + + def _transform(self, observation): if self.player_count > 1: - transformed_obs['obs'] = [self._obs_transform(obs) for obs in observation['obs']] + transformed_obs = [obs.transpose(1, 2, 0).astype(np.float) for obs in observation] else: - transformed_obs['obs'] = self._obs_transform(observation['obs']) + transformed_obs = observation.transpose(1, 2, 0).astype(np.float) - if self._invalid_action_masking: - transformed_obs['valid_action_tree'] = + return transformed_obs def set_transform(self): """ @@ -168,28 +116,25 @@ def render(self, mode='human', observer=0): class RLlibMultiAgentWrapper(RLlibWrapper, MultiAgentEnv): def __init__(self, env_config): - super().__init__(**env_config) + super().__init__(env_config) self._player_done_variable = env_config.get('player_done_variable', None) assert self.player_count > 1, 'RLlibMultiAgentWrapper can only be used with environments that have multiple agents' - def _to_rlib_obs(self, obs_array): - return {p: obs for p, obs in enumerate(obs_array)} + def _to_multi_agent_map(self, data): + return {p: obs for p, obs in enumerate(data)} def reset(self, **kwargs): obs = super().reset(**kwargs) - return self._to_rlib_obs(obs) - - def get_info(self): - return self.info + return self._to_multi_agent_map(obs) def step(self, action_dict: MultiAgentDict): actions_array = np.zeros((self.player_count, *self.action_space.shape)) for agent_id, action in action_dict.items(): actions_array[agent_id] = action - obs, reward, all_done, self.info = super().step(actions_array) + obs, reward, all_done, _ = super().step(actions_array) done = {'__all__': all_done} @@ -199,4 +144,8 @@ def step(self, action_dict: MultiAgentDict): for p in range(self.player_count): done[p + 1] = False - return self._to_rlib_obs(obs), self._to_rlib_obs(reward), done, {} + info = {} + if self._invalid_action_masking: + info = self._to_multi_agent_map(self._build_valid_action_trees()) + + return self._to_multi_agent_map(obs), self._to_multi_agent_map(reward), done, info From 599acd4b225c744550076a07d590a1415a98bc90 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 6 Feb 2021 17:06:47 +0000 Subject: [PATCH 04/34] invalid action masking wrapper appears to be working but rewards might be broken need to investigate --- python/examples/rllib/rllib_RTS.py | 4 +- .../torch/conditional_masking_distribution.py | 53 ++++++++++++++++ .../rllib/torch/invalid_action_exploration.py | 24 -------- python/griddly/util/rllib/torch/mixins.py | 61 ++++++++++++++----- python/griddly/util/rllib/wrappers.py | 7 ++- 5 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 python/griddly/util/rllib/torch/conditional_masking_distribution.py delete mode 100644 python/griddly/util/rllib/torch/invalid_action_exploration.py diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index 6fd29070d..e8afd3e5a 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -64,9 +64,9 @@ def get_policy_class(config): 'env': env_name, 'env_config': { # Tell the RLlib wrapper to use invalid action masking - 'invalid_action_masking': True, + 'invalid_action_masking': False, - 'yaml_file': 'RTS/Stratega/heal-or-die.yaml', + 'yaml_file': 'RTS/GriddlyRTS.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 0, 'max_steps': 1000, diff --git a/python/griddly/util/rllib/torch/conditional_masking_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_distribution.py new file mode 100644 index 000000000..6d568270e --- /dev/null +++ b/python/griddly/util/rllib/torch/conditional_masking_distribution.py @@ -0,0 +1,53 @@ +from typing import Union +import numpy as np +import torch +from torch.distributions import Categorical + + +class TorchConditionalMaskingExploration(): + + def __init__(self, dist_inputs, valid_action_trees, dist_class): + self._valid_action_trees = valid_action_trees + self._dist_class = dist_class + + self._num_inputs = dist_inputs.shape[0] + self._action_space_shape = dist_class.keywords['input_lens'] + self._num_action_logits = np.sum(self._action_space_shape) + self._num_action_parts = len(self._action_space_shape) + + self._inputs_split = dist_inputs.split(tuple(self._action_space_shape), dim=1) + + def _mask_and_sample(self, options, logits): + mask = torch.zeros([logits.shape[0]]) + mask[options] = 1 + + logits += torch.log(mask) + dist = Categorical(logits=logits) + sampled = dist.sample() + + return sampled, logits + + def get_actions_and_mask(self): + + actions = torch.zeros([self._num_inputs, self._num_action_parts]) + masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) + + for i in range(self._num_inputs): + # just do nothing if we have no action tree, also no gradients are propagated because mask is 0 + if len(self._valid_action_trees) >= 1: + subtree = self._valid_action_trees[i] + subtree_options = list(subtree.keys()) + mask_offset = 0 + for a in range(self._num_action_parts): + dist_part = self._inputs_split[a] + sampled, masked_logits_part = self._mask_and_sample(subtree_options, dist_part[i]) + masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part + if isinstance(subtree, dict): + subtree = subtree[int(sampled)] + if isinstance(subtree, dict): + subtree_options = list(subtree.keys()) + else: + # Leaf nodes with action_id list + subtree_options = subtree + + return actions, masked_logits diff --git a/python/griddly/util/rllib/torch/invalid_action_exploration.py b/python/griddly/util/rllib/torch/invalid_action_exploration.py deleted file mode 100644 index bf2af2b4e..000000000 --- a/python/griddly/util/rllib/torch/invalid_action_exploration.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Union - -from ray.rllib.models import ActionDistribution -from ray.rllib.utils.exploration import StochasticSampling -from ray.rllib.utils.framework import TensorType - - -class InvalidActionMaskingPolicyWrapper(): - - @staticmethod - def action_sampler_fn(policy, model, input_dict, state_out, explore, timestep): - - location_mask = input_dict['location_action_mask'] - action_masks = input_dict['unit_action_mask'] - - # Firstly sample from the location mask - - - # Use the sampled location to retrieve the unit action mask - - - @staticmethod - def wrap(policy_cls, name): - return policy_cls.with_updates(name, action_sampler_fn=InvalidActionMaskingPolicyWrapper.action_sampler_fn) diff --git a/python/griddly/util/rllib/torch/mixins.py b/python/griddly/util/rllib/torch/mixins.py index 24a3a500c..6c5058613 100644 --- a/python/griddly/util/rllib/torch/mixins.py +++ b/python/griddly/util/rllib/torch/mixins.py @@ -1,16 +1,22 @@ +import functools from typing import Callable, Dict, List, Optional, Tuple, Type, Union +import tree from ray.rllib import SampleBatch, Policy +from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper from ray.rllib.utils import override -from ray.rllib.utils.torch_ops import convert_to_torch_tensor +from ray.rllib.utils.torch_ops import convert_to_torch_tensor, convert_to_non_torch_type from ray.rllib.utils.typing import TensorType import numpy as np import torch +from griddly.util.rllib.torch.conditional_masking_distribution import TorchConditionalMaskingExploration + class InvalidActionMaskingPolicyMixin: + @override(Policy) def compute_actions( self, @@ -45,17 +51,42 @@ def compute_actions( for s in (state_batches or []) ] - return self._compute_action_helper(input_dict, state_batches, - seq_lens, explore, timestep) - - # @override(Policy) - # def compute_actions_from_input_dict( - # self, - # input_dict: Dict[str, TensorType], - # explore: bool = None, - # timestep: Optional[int] = None, - # episodes: Optional[List["MultiAgentEpisode"]] = None, - # **kwargs) -> \ - # Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: - # raise NotImplementedError('Invalid action masking cannot be used with Trajectory View API. please set ' - # '"_use_trajectory_view_api": False, in the training config.') + + # Call the exploration before_compute_actions hook. + self.exploration.before_compute_actions( + explore=explore, timestep=timestep) + + dist_inputs, state_out = self.model(input_dict, state_batches, + seq_lens) + # Extract the tree from the info batch + valid_action_trees = [v['valid_action_tree'] for v in info_batch if 'valid_action_tree' in v] + + exploration = TorchConditionalMaskingExploration(dist_inputs, valid_action_trees, self.dist_class) + actions, masked_dist_actions = exploration.get_actions_and_mask() + + masked_action_dist = self.dist_class(masked_dist_actions, self.model) + + logp = masked_action_dist.logp(actions) + + input_dict[SampleBatch.ACTIONS] = actions + + # Add default and custom fetches. + extra_fetches = self.extra_action_out(input_dict, state_batches, + self.model, masked_action_dist) + + # Action-dist inputs. + if dist_inputs is not None: + extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_dist_actions + + # Action-logp and action-prob. + if logp is not None: + extra_fetches[SampleBatch.ACTION_PROB] = \ + torch.exp(logp.float()) + extra_fetches[SampleBatch.ACTION_LOGP] = logp + + # Update our global timestep by the batch size. + self.global_timestep += len(input_dict[SampleBatch.CUR_OBS]) + + return convert_to_non_torch_type((actions, state_out, extra_fetches)) + + diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index 368c7fc13..3138949d5 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -65,7 +65,8 @@ def _get_player_action_tree(self, player_id): valid_action_tree = defaultdict(lambda: defaultdict(lambda: defaultdict(defaultdict))) for location, action_names in self.game.get_available_actions(player_id).items(): for action_name, action_ids in self.game.get_available_action_ids(location, list(action_names)).items(): - valid_action_tree[location[0]][location[1]][self.action_names.index(action_name)] = action_ids + if len(action_ids) > 0: + valid_action_tree[location[0]][location[1]][self.action_names.index(action_name)] = action_ids return valid_action_tree def _build_valid_action_trees(self): @@ -73,7 +74,7 @@ def _build_valid_action_trees(self): if self.player_count > 0: for p in range(self.player_count): - player_valid_action_trees.append({'valid_action_tree':self._get_player_action_tree(p + 1)}) + player_valid_action_trees.append({'valid_action_tree': self._get_player_action_tree(p + 1)}) else: player_valid_action_trees.append({'valid_action_tree': self._get_player_action_tree(1)}) @@ -146,6 +147,6 @@ def step(self, action_dict: MultiAgentDict): info = {} if self._invalid_action_masking: - info = self._to_multi_agent_map(self._build_valid_action_trees()) + info = self._to_multi_agent_map(self._build_valid_action_trees()) return self._to_multi_agent_map(obs), self._to_multi_agent_map(reward), done, info From 877779827d52718e96608862cc60103cbc3ef4d7 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 7 Feb 2021 16:28:35 +0000 Subject: [PATCH 05/34] progress on invalid action masking --- python/examples/rllib/rllib_RTS.py | 31 ++--- python/examples/rllib/test_rts.yaml | 106 ++++++++++++++++++ .../torch/conditional_masking_distribution.py | 5 + python/griddly/util/rllib/torch/mixins.py | 11 +- 4 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 python/examples/rllib/test_rts.yaml diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index e8afd3e5a..d66582a88 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -15,11 +15,6 @@ from griddly.util.rllib.torch.mixins import InvalidActionMaskingPolicyMixin -def setup_invalid_mask_mixin(policy, obs_space, action_space, config): - InvalidActionMaskingPolicyMixin.__init__(policy) - setup_mixins(policy, obs_space, action_space, config) - - if __name__ == '__main__': sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) @@ -31,6 +26,19 @@ def setup_invalid_mask_mixin(policy, obs_space, action_space, config): register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) + + def setup_invalid_mask_mixin(policy, obs_space, action_space, config): + InvalidActionMaskingPolicyMixin.__init__(policy) + setup_mixins(policy, obs_space, action_space, config) + + + def get_policy_class(config): + if config['framework'] == 'torch': + return InvalidActionMaskingTorchPolicy + else: + raise NotImplementedError('Tensorflow not supported') + + InvalidActionMaskingTorchPolicy = VTraceTorchPolicy.with_updates( name='InvalidActionMaskingTorchPolicy', before_init=setup_invalid_mask_mixin, @@ -41,14 +49,11 @@ def setup_invalid_mask_mixin(policy, obs_space, action_space, config): ]) - def get_policy_class(config): - if config['framework'] == 'torch': - return InvalidActionMaskingTorchPolicy - - InvalidActionMaskingImpalaTrainer = ImpalaTrainer.with_updates(default_policy=InvalidActionMaskingTorchPolicy, get_policy_class=get_policy_class) + test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_rts.yaml') + config = { 'framework': 'torch', 'num_workers': 8, @@ -63,10 +68,10 @@ def get_policy_class(config): }, 'env': env_name, 'env_config': { - # Tell the RLlib wrapper to use invalid action masking - 'invalid_action_masking': False, + # Tell the RLlib wrapper to use invalid action masking + 'invalid_action_masking': True, - 'yaml_file': 'RTS/GriddlyRTS.yaml', + 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 0, 'max_steps': 1000, diff --git a/python/examples/rllib/test_rts.yaml b/python/examples/rllib/test_rts.yaml new file mode 100644 index 000000000..e49bd5faf --- /dev/null +++ b/python/examples/rllib/test_rts.yaml @@ -0,0 +1,106 @@ +Version: "0.1" +Environment: + Name: TestRTS + Description: An RTS Game. There's aliens and stuff. + Observers: + Sprite2D: + TileSize: 16 + BackgroundTile: oryx/oryx_tiny_galaxy/tg_sliced/tg_world/tg_world_floor_panel_metal_a.png + Isometric: + TileSize: [32, 48] + BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png + IsoTileHeight: 16 + IsoTileDepth: 4 + Variables: + - Name: player_resources + InitialValue: 0 + PerPlayer: true + Player: + Count: 2 + Termination: + Win: + - eq: [_score, 10] # First player to 10 reward points + Levels: + - | + W W W W W W W W W W W W + W . . H2 . . . . . . . W + W . H2 . . . . . . . . W + W H2 . . . M M . . . . W + W . . . M M M M . . . W + W . . . . M M . . . H1 W + W . . . . . . . . H1 . W + W . . . . . . . H1 . . W + W W W W W W W W W W W W + +Actions: + - Name: gather + Behaviours: + - Src: + Object: harvester + Commands: + - incr: resources + - reward: 1 + Dst: + Object: minerals + Commands: + - decr: resources + - eq: + Arguments: [resources, 0] + Commands: + - remove: true + + - Name: move + Behaviours: + - Src: + Object: harvester + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + +Objects: + - Name: minerals + MapCharacter: M + Variables: + - Name: resources + InitialValue: 5 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_crystal_green.png + Block2D: + - Shape: triangle + Color: [0.0, 1.0, 0.0] + Scale: 1.0 + Isometric: + - Image: oryx/oryx_iso_dungeon/minerals-1.png + + - Name: harvester + MapCharacter: H + Variables: + - Name: resources + InitialValue: 0 + - Name: health + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_jelly_d1.png + Block2D: + - Shape: square + Color: [0.6, 0.2, 0.2] + Scale: 0.5 + Isometric: + - Image: oryx/oryx_iso_dungeon/jelly-1.png + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_2 # Will tile walls with two images + Image: + - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img33.png + - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img40.png + Block2D: + - Color: [0.5, 0.5, 0.5] + Shape: square + Isometric: + - Image: oryx/oryx_iso_dungeon/wall-grey-1.png diff --git a/python/griddly/util/rllib/torch/conditional_masking_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_distribution.py index 6d568270e..d8c8e8e6b 100644 --- a/python/griddly/util/rllib/torch/conditional_masking_distribution.py +++ b/python/griddly/util/rllib/torch/conditional_masking_distribution.py @@ -35,13 +35,18 @@ def get_actions_and_mask(self): for i in range(self._num_inputs): # just do nothing if we have no action tree, also no gradients are propagated because mask is 0 if len(self._valid_action_trees) >= 1: + subtree = self._valid_action_trees[i] subtree_options = list(subtree.keys()) mask_offset = 0 for a in range(self._num_action_parts): dist_part = self._inputs_split[a] sampled, masked_logits_part = self._mask_and_sample(subtree_options, dist_part[i]) + + # Set the action and the mask for each part of the action + actions[i, a] = sampled masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part + if isinstance(subtree, dict): subtree = subtree[int(sampled)] if isinstance(subtree, dict): diff --git a/python/griddly/util/rllib/torch/mixins.py b/python/griddly/util/rllib/torch/mixins.py index 6c5058613..77e1e830c 100644 --- a/python/griddly/util/rllib/torch/mixins.py +++ b/python/griddly/util/rllib/torch/mixins.py @@ -31,6 +31,10 @@ def compute_actions( **kwargs) -> \ Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: + + if not self.config['env_config'].get('invalid_action_masking', False): + raise RuntimeError('invalid_action_masking must be set to True in env_config to use this mixin') + explore = explore if explore is not None else self.config["explore"] timestep = timestep if timestep is not None else self.global_timestep @@ -59,7 +63,12 @@ def compute_actions( dist_inputs, state_out = self.model(input_dict, state_batches, seq_lens) # Extract the tree from the info batch - valid_action_trees = [v['valid_action_tree'] for v in info_batch if 'valid_action_tree' in v] + valid_action_trees = [] + for info in info_batch: + if 'valid_action_tree' in info: + valid_action_trees.append(info['valid_action_tree']) + else: + valid_action_trees.append({0:{0:{0:[0]}}}) exploration = TorchConditionalMaskingExploration(dist_inputs, valid_action_trees, self.dist_class) actions, masked_dist_actions = exploration.get_actions_and_mask() From ecc9e1ea4a493acd5e4e504819b9a42ba8ebc4fc Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 8 Feb 2021 20:21:22 +0000 Subject: [PATCH 06/34] vector observer additional options for multi-agent games --- bindings/wrapper/GameWrapper.cpp | 2 +- python/examples/rllib/test_rts.yaml | 20 ++- src/Griddly/Core/GameProcess.cpp | 6 +- src/Griddly/Core/GameProcess.hpp | 2 +- src/Griddly/Core/Grid.cpp | 13 +- src/Griddly/Core/Grid.hpp | 15 +- .../Core/LevelGenerators/MapReader.cpp | 7 +- src/Griddly/Core/Observers/Observer.hpp | 6 + src/Griddly/Core/Observers/VectorObserver.cpp | 133 ++++++++++++------ src/Griddly/Core/Observers/VectorObserver.hpp | 5 + src/Griddly/Core/TurnBasedGameProcess.cpp | 10 +- .../Griddly/Core/GDY/Objects/ObjectTest.cpp | 9 +- .../Core/GDY/TerminationHandlerTest.cpp | 48 ++++++- tests/src/Griddly/Core/GameProcessTest.cpp | 10 +- tests/src/Griddly/Core/GridTest.cpp | 44 +++--- .../Core/LevelGenerator/MapReaderTest.cpp | 32 +++-- .../Core/Observers/VectorObserverTest.cpp | 102 +++++++++++++- tests/src/Mocks/Griddly/Core/MockGrid.hpp | 4 +- 18 files changed, 367 insertions(+), 101 deletions(-) diff --git a/bindings/wrapper/GameWrapper.cpp b/bindings/wrapper/GameWrapper.cpp index 55cd3bb31..994159582 100644 --- a/bindings/wrapper/GameWrapper.cpp +++ b/bindings/wrapper/GameWrapper.cpp @@ -217,7 +217,7 @@ class Py_GameWrapper { py::dict getState() const { py::dict py_state; - auto& state = gameProcess_->getState(); + auto state = gameProcess_->getState(); py_state["GameTicks"] = state.gameTicks; diff --git a/python/examples/rllib/test_rts.yaml b/python/examples/rllib/test_rts.yaml index e49bd5faf..8f3d4aec8 100644 --- a/python/examples/rllib/test_rts.yaml +++ b/python/examples/rllib/test_rts.yaml @@ -19,7 +19,7 @@ Environment: Count: 2 Termination: Win: - - eq: [_score, 10] # First player to 10 reward points + - eq: [_score, 100] # First player to 10 reward points Levels: - | W W W W W W W W W W W W @@ -49,6 +49,24 @@ Actions: Commands: - remove: true + + # Steal resources from other players + - Src: + Preconditions: + - neq: [ src._playerId, dst._playerId ] + - gt: [dst.resources, 0] + Object: harvester + Commands: + - incr: resources + - reward: 1 + Dst: + Object: harvester + Commands: + - decr: resources + - reward: -1 + + + - Name: move Behaviours: - Src: diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index 5d6c2be82..f1743796b 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -267,14 +267,14 @@ std::vector GameProcess::getAvailableActionIdsAtLocation(glm::ivec2 lo return availableActionIds; } -StateInfo& GameProcess::getState() const { +StateInfo GameProcess::getState() const { StateInfo stateInfo; stateInfo.gameTicks = *grid_->getTickCount(); - auto globalVariables = grid_->getGlobalVariables(); + auto& globalVariables = grid_->getGlobalVariables(); - for (auto globalVarIt : globalVariables) { + for (auto& globalVarIt : globalVariables) { auto variableName = globalVarIt.first; auto variableValues = globalVarIt.second; for (auto variableValue : variableValues) { diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index 6ee749af6..fc347d95f 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -63,7 +63,7 @@ class GameProcess : public std::enable_shared_from_this { virtual std::vector getAvailableActionIdsAtLocation(glm::ivec2 location, std::string actionName) const; - virtual StateInfo& getState() const; + virtual StateInfo getState() const; virtual uint32_t getNumPlayers() const; diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index cc6866e57..caf4b66b0 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -34,6 +34,7 @@ void Grid::resetMap(uint32_t width, uint32_t height) { objects_.clear(); objectCounters_.clear(); objectNames_.clear(); + objectVariableNames_.clear(); delayedActions_ = {}; gameTicks_ = std::make_shared(0); @@ -304,8 +305,18 @@ uint32_t Grid::getUniqueObjectCount() const { return objectNames_.size(); } -void Grid::initObject(std::string objectName) { +const std::set& Grid::getObjectNames() const { + return objectNames_; +} + +const std::set& Grid::getObjectVariableNames() const { + return objectVariableNames_; +} + + +void Grid::initObject(std::string objectName, std::vector variableNames) { objectNames_.insert(objectName); + objectVariableNames_.insert(variableNames.begin(), variableNames.end()); } std::unordered_map> Grid::getObjectCounter(std::string objectName) { diff --git a/src/Griddly/Core/Grid.hpp b/src/Griddly/Core/Grid.hpp index 334bd08c7..545b4bffd 100644 --- a/src/Griddly/Core/Grid.hpp +++ b/src/Griddly/Core/Grid.hpp @@ -77,7 +77,7 @@ class Grid : public std::enable_shared_from_this { virtual std::shared_ptr getTickCount() const; virtual void setTickCount(int32_t tickCount); - virtual void initObject(std::string objectName); + virtual void initObject(std::string objectName, std::vector objectVariableNames); virtual void addObject(glm::ivec2 location, std::shared_ptr object, bool applyInitialActions = true); virtual bool removeObject(std::shared_ptr object); @@ -98,6 +98,16 @@ class Grid : public std::enable_shared_from_this { */ virtual uint32_t getUniqueObjectCount() const; + /** + * Gets an ordered list of objectVariableNames + */ + virtual const std::set& getObjectVariableNames() const; + + /** + * Gets an ordered list of objectNames + */ + virtual const std::set& getObjectNames() const; + /** * Get a mapping of the avatar objects for players in the environment */ @@ -126,7 +136,8 @@ class Grid : public std::enable_shared_from_this { // This is so we can highly optimize observers to only re-render changed grid locations std::vector> updatedLocations_; - std::unordered_set objectNames_; + std::set objectNames_; + std::set objectVariableNames_; std::unordered_set> objects_; std::unordered_map occupiedLocations_; std::unordered_map>> objectCounters_; diff --git a/src/Griddly/Core/LevelGenerators/MapReader.cpp b/src/Griddly/Core/LevelGenerators/MapReader.cpp index d4a39a97e..90bff7747 100644 --- a/src/Griddly/Core/LevelGenerators/MapReader.cpp +++ b/src/Griddly/Core/LevelGenerators/MapReader.cpp @@ -19,12 +19,15 @@ MapReader::~MapReader() { } void MapReader::reset(std::shared_ptr grid) { - grid->resetMap(width_, height_); for (auto objectDefinition : objectGenerator_->getObjectDefinitions()) { auto objectName = objectDefinition.second->objectName; - grid->initObject(objectName); + std::vector objectVariableNames; + for (auto variableNameIt : objectDefinition.second->variableDefinitions) { + objectVariableNames.push_back(variableNameIt.first); + } + grid->initObject(objectName, objectVariableNames); spdlog::debug("Initializing object {0}", objectName); } diff --git a/src/Griddly/Core/Observers/Observer.hpp b/src/Griddly/Core/Observers/Observer.hpp index c2107b921..1d1a552de 100644 --- a/src/Griddly/Core/Observers/Observer.hpp +++ b/src/Griddly/Core/Observers/Observer.hpp @@ -17,6 +17,12 @@ struct ObserverConfig { uint32_t isoTileDepth = 0; uint32_t isoTileHeight = 0; glm::ivec2 tileSize = {24, 24}; + + + // Config for VECTOR observers only + bool includeVariables = false; + bool includeOrientation = false; + bool includePlayerId = false; }; struct PartialObservableGrid { diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index 8bcd70756..472fb1ecd 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -12,6 +12,8 @@ VectorObserver::~VectorObserver() {} void VectorObserver::init(ObserverConfig observerConfig) { Observer::init(observerConfig); + + isPartiallyObservable_ = avatarObject_ != nullptr; } ObserverType VectorObserver::getObserverType() const { @@ -29,12 +31,26 @@ void VectorObserver::resetShape() { gridBoundary_.x = grid_->getWidth(); gridBoundary_.y = grid_->getHeight(); - auto uniqueObjectCount = grid_->getUniqueObjectCount(); + observationChannels_ = grid_->getUniqueObjectCount(); + + // Always in order objects, player, orientation, variables. + + if (observerConfig_.includePlayerId) { + observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" + } + + if (observerConfig_.includeOrientation) { + observationChannels_ += 4; + } + + if (observerConfig_.includeVariables) { + observationChannels_ += grid_->getObjectVariableNames().size(); + } - observationShape_ = {uniqueObjectCount, gridWidth_, gridHeight_}; - observationStrides_ = {1, uniqueObjectCount, uniqueObjectCount * gridWidth_}; + observationShape_ = {observationChannels_, gridWidth_, gridHeight_}; + observationStrides_ = {1, observationChannels_, observationChannels_ * gridWidth_}; - observation_ = std::shared_ptr(new uint8_t[uniqueObjectCount * gridWidth_ * gridHeight_]{}); + observation_ = std::shared_ptr(new uint8_t[observationChannels_ * gridWidth_ * gridHeight_]{}); } uint8_t* VectorObserver::reset() { @@ -42,16 +58,77 @@ uint8_t* VectorObserver::reset() { return update(); }; -uint8_t* VectorObserver::update() const { +void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 outputLocation, bool resetLocation) const { auto uniqueObjectCount = grid_->getUniqueObjectCount(); - if (avatarObject_ != nullptr) { + auto memPtr = observation_.get() + observationChannels_ * (gridWidth_ * outputLocation.y + outputLocation.x); + + if (resetLocation) { + auto size = sizeof(uint8_t) * observationChannels_; + memset(memPtr, 0, size); + } + + // Only put the *include* information of the first object + bool processTopLayer = false; + for (auto& objectIt : grid_->getObjectsAt(objectLocation)) { + auto object = objectIt.second; + auto memPtrObject = memPtr + object->getObjectId(); + *memPtrObject = 1; + + if (processTopLayer) { + if (observerConfig_.includePlayerId) { + auto playerMemPtr = memPtr + uniqueObjectCount + object->getPlayerId(); + *playerMemPtr = 1; + } + + if (observerConfig_.includeOrientation) { + uint32_t directionIdx = 0; + switch (object->getObjectOrientation().getDirection()) { + case Direction::UP: + case Direction::NONE: + directionIdx = 0; + case Direction::RIGHT: + directionIdx = 1; + case Direction::DOWN: + directionIdx = 2; + case Direction::LEFT: + directionIdx = 3; + } + auto orientationMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + directionIdx; + *orientationMemPtr = 1; + } + + if (observerConfig_.includeVariables) { + for (auto& variableIt : object->getAvailableVariables()) { + auto variableValue = *variableIt.second; + auto variableName = variableIt.first; + + // If the variable is one of the variables defined in the object, get the index of the variable and set it to the variable's value + auto objectVariableIt = grid_->getObjectVariableNames().find(variableName); + if (objectVariableIt != grid_->getObjectVariableNames().end()) { + uint32_t variableIdx = std::distance(grid_->getObjectVariableNames().begin(), grid_->getObjectVariableNames().begin()); + + auto variableMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 4 + variableIdx; + *variableMemPtr = variableValue; + } else { + throw std::runtime_error("Available variable not defined."); + } + } + } + + processTopLayer = false; + } + } +} + +uint8_t* VectorObserver::update() const { + if (isPartiallyObservable_) { auto avatarLocation = avatarObject_->getLocation(); auto avatarOrientation = avatarObject_->getObjectOrientation(); auto avatarDirection = avatarOrientation.getDirection(); // Have to reset the observation - auto size = sizeof(uint8_t) * uniqueObjectCount * gridWidth_ * gridHeight_; + auto size = sizeof(uint8_t) * observationChannels_ * gridWidth_ * gridHeight_; memset(observation_.get(), 0, size); if (observerConfig_.rotateWithAvatar) { @@ -65,11 +142,7 @@ uint8_t* VectorObserver::update() const { for (auto objx = pGrid.left; objx <= pGrid.right; objx++) { outy = 0; for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { - for (auto objectIt : grid_->getObjectsAt({objx, objy})) { - auto object = objectIt.second; - int idx = uniqueObjectCount * (gridWidth_ * outy + outx) + object->getObjectId(); - observation_.get()[idx] = 1; - } + renderLocation({objx, objy}, {outx, outy}); outy++; } outx++; @@ -80,11 +153,7 @@ uint8_t* VectorObserver::update() const { for (auto objx = pGrid.left; objx <= pGrid.right; objx++) { outy = gridHeight_ - 1; for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { - for (auto objectIt : grid_->getObjectsAt({objx, objy})) { - auto object = objectIt.second; - int idx = uniqueObjectCount * (gridWidth_ * outy + outx) + object->getObjectId(); - observation_.get()[idx] = 1; - } + renderLocation({objx, objy}, {outx, outy}); outy--; } outx--; @@ -95,11 +164,7 @@ uint8_t* VectorObserver::update() const { for (auto objx = pGrid.left; objx <= pGrid.right; objx++) { outx = 0; for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { - for (auto objectIt : grid_->getObjectsAt({objx, objy})) { - auto object = objectIt.second; - int idx = uniqueObjectCount * (gridWidth_ * outy + outx) + object->getObjectId(); - observation_.get()[idx] = 1; - } + renderLocation({objx, objy}, {outx, outy}); outx++; } outy--; @@ -109,11 +174,7 @@ uint8_t* VectorObserver::update() const { for (auto objx = pGrid.left; objx <= pGrid.right; objx++) { outx = gridWidth_ - 1; for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { - for (auto objectIt : grid_->getObjectsAt({objx, objy})) { - auto object = objectIt.second; - int idx = uniqueObjectCount * (gridWidth_ * outy + outx) + object->getObjectId(); - observation_.get()[idx] = 1; - } + renderLocation({objx, objy}, {outx, outy}); outx--; } outy++; @@ -130,11 +191,7 @@ uint8_t* VectorObserver::update() const { for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { if (objx < gridBoundary_.x && objx >= 0 && objy < gridBoundary_.y && objy >= 0) { // place a 1 in every object "slice" where that object appears - for (auto objectIt : grid_->getObjectsAt({objx, objy})) { - auto object = objectIt.second; - int idx = uniqueObjectCount * (gridWidth_ * outy + outx) + object->getObjectId(); - observation_.get()[idx] = 1; - } + renderLocation({objx, objy}, {outx, outy}); } outy++; } @@ -154,17 +211,7 @@ uint8_t* VectorObserver::update() const { location.y - observerConfig_.gridYOffset); if (outputLocation.x < gridWidth_ && outputLocation.x >= 0 && outputLocation.y < gridHeight_ && outputLocation.y >= 0) { - auto memPtr = observation_.get() + uniqueObjectCount * (gridWidth_ * outputLocation.y + outputLocation.x); - - auto size = sizeof(uint8_t) * uniqueObjectCount; - memset(memPtr, 0, size); - - auto& objects = grid_->getObjectsAt(location); - for (auto objectIt : objects) { - auto object = objectIt.second; - auto memPtrObject = memPtr + object->getObjectId(); - *memPtrObject = 1; - } + renderLocation(location, outputLocation, true); } } } diff --git a/src/Griddly/Core/Observers/VectorObserver.hpp b/src/Griddly/Core/Observers/VectorObserver.hpp index 1d1e2be63..6329825d2 100644 --- a/src/Griddly/Core/Observers/VectorObserver.hpp +++ b/src/Griddly/Core/Observers/VectorObserver.hpp @@ -19,8 +19,13 @@ class VectorObserver : public Observer { void print(std::shared_ptr observation) override; + protected: + void renderLocation(glm::ivec2 objectLocation, glm::ivec2 outputLocation, bool resetLocation=false) const; + private: std::shared_ptr observation_; + bool isPartiallyObservable_; + uint32_t observationChannels_; }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/TurnBasedGameProcess.cpp b/src/Griddly/Core/TurnBasedGameProcess.cpp index 80587419c..a19ff9bad 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.cpp +++ b/src/Griddly/Core/TurnBasedGameProcess.cpp @@ -93,14 +93,18 @@ std::shared_ptr TurnBasedGameProcess::clone() { spdlog::debug("Cloning objects types..."); for (auto objectDefinition : objectGenerator->getObjectDefinitions()) { auto objectName = objectDefinition.second->objectName; - clonedGrid->initObject(objectName); + std::vector objectVariableNames; + for (auto variableNameIt : objectDefinition.second->variableDefinitions) { + objectVariableNames.push_back(variableNameIt.first); + } + clonedGrid->initObject(objectName, objectVariableNames); } // Clone Objects spdlog::debug("Cloning objects..."); - auto objectsToCopy = grid_->getObjects(); + auto& objectsToCopy = grid_->getObjects(); std::unordered_map, std::shared_ptr> clonedObjectMapping; - for (auto toCopy : objectsToCopy) { + for (const auto& toCopy : objectsToCopy) { auto clonedObject = objectGenerator->cloneInstance(toCopy, clonedGrid->getGlobalVariables()); clonedGrid->addObject(toCopy->getLocation(), clonedObject, false); diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index 8e0bad5d8..b392f0915 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -17,6 +17,7 @@ using ::testing::Eq; using ::testing::Invoke; using ::testing::Mock; using ::testing::Return; +using ::testing::ReturnRef; namespace griddly { @@ -936,11 +937,11 @@ TEST(ObjectTest, command_change_to) { auto mockActionPtr = setupAction("action", srcObjectPtr, dstObjectPtr); EXPECT_CALL(*mockGridPtr, getGlobalVariables) - .WillRepeatedly(Return(globalVariables)); + .WillRepeatedly(ReturnRef(globalVariables)); EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(1), Eq(globalVariables))) .WillOnce(Return(newObjectPtr)); - + EXPECT_CALL(*mockObjectGenerator, newInstance(Eq("newObject"), Eq(2), Eq(globalVariables))) .WillOnce(Return(newObjectPtr)); @@ -1021,6 +1022,10 @@ TEST(ObjectTest, command_spawn) { EXPECT_CALL(*mockGridPtr, addObject(Eq(glm::ivec2(1, 0)), Eq(newObjectPtr), Eq(true))) .Times(1); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables) + .WillOnce(ReturnRef(globalVariables)); + auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "spawn", {{"0", _Y("newObject")}}, srcObjectPtr, nullptr); verifyCommandResult(srcResult, false, 0); diff --git a/tests/src/Griddly/Core/GDY/TerminationHandlerTest.cpp b/tests/src/Griddly/Core/GDY/TerminationHandlerTest.cpp index b31304605..9a732e373 100644 --- a/tests/src/Griddly/Core/GDY/TerminationHandlerTest.cpp +++ b/tests/src/Griddly/Core/GDY/TerminationHandlerTest.cpp @@ -12,6 +12,7 @@ using ::testing::Eq; using ::testing::Mock; using ::testing::Pair; using ::testing::Return; +using ::testing::ReturnRef; using ::testing::UnorderedElementsAre; namespace griddly { @@ -38,6 +39,11 @@ TEST(TerminationHandlerTest, terminateOnPlayerScore) { EXPECT_CALL(*mockPlayer2Ptr, getScore()) .WillRepeatedly(Return(player2Score)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto terminationHandlerPtr = std::shared_ptr(new TerminationHandler(mockGridPtr, players)); TerminationConditionDefinition tcd; @@ -67,6 +73,11 @@ TEST(TerminationHandlerTest, terminateOnPlayerObjects0) { .Times(1) .WillOnce(Return(mockBaseCounter)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + EXPECT_CALL(*mockPlayer1Ptr, getId()) .WillRepeatedly(Return(1)); @@ -111,7 +122,7 @@ TEST(TerminationHandlerTest, terminateOnGlobalVariable) { EXPECT_CALL(*mockGridPtr, getGlobalVariables()) .Times(1) - .WillOnce(Return(globalVariables)); + .WillOnce(ReturnRef(globalVariables)); EXPECT_CALL(*mockPlayer1Ptr, getId()) .WillRepeatedly(Return(1)); @@ -160,7 +171,7 @@ TEST(TerminationHandlerTest, terminateOnPlayerGlobalVariable) { EXPECT_CALL(*mockGridPtr, getGlobalVariables()) .Times(1) - .WillOnce(Return(globalVariables)); + .WillOnce(ReturnRef(globalVariables)); EXPECT_CALL(*mockPlayer1Ptr, getId()) .WillRepeatedly(Return(1)); @@ -207,16 +218,21 @@ TEST(TerminationHandlerTest, terminateOnMaxTicks) { EXPECT_CALL(*mockPlayer2Ptr, getId()) .WillRepeatedly(Return(2)); - EXPECT_CALL(*mockGridPtr, getTickCount()) - .Times(1) - .WillOnce(Return(tickCounter)); - EXPECT_CALL(*mockPlayer1Ptr, getScore()) .WillRepeatedly(Return(player1Score)); EXPECT_CALL(*mockPlayer2Ptr, getScore()) .WillRepeatedly(Return(player2Score)); + EXPECT_CALL(*mockGridPtr, getTickCount()) + .Times(1) + .WillOnce(Return(tickCounter)); + + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto terminationHandlerPtr = std::shared_ptr(new TerminationHandler(mockGridPtr, players)); TerminationConditionDefinition tcd; @@ -240,6 +256,11 @@ TEST(TerminationHandlerTest, singlePlayer_differentId_win) { EXPECT_CALL(*mockPlayer1Ptr, getId()) .WillRepeatedly(Return(1)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto playerObjectCounter = std::unordered_map>{{0, std::make_shared(0)}}; EXPECT_CALL(*mockGridPtr, getObjectCounter(Eq("environment_objects"))) .Times(1) @@ -268,6 +289,11 @@ TEST(TerminationHandlerTest, singlePlayer_differentId_lose) { EXPECT_CALL(*mockPlayer1Ptr, getId()) .WillRepeatedly(Return(1)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto playerObjectCounter = std::unordered_map>{{0, std::make_shared(0)}}; EXPECT_CALL(*mockGridPtr, getObjectCounter(Eq("environment_objects"))) .Times(1) @@ -301,6 +327,11 @@ TEST(TerminationHandlerTest, singlePlayer_sameId_lose) { .Times(1) .WillOnce(Return(playerObjectCounter)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto terminationHandlerPtr = std::shared_ptr(new TerminationHandler(mockGridPtr, players)); TerminationConditionDefinition tcd; @@ -329,6 +360,11 @@ TEST(TerminationHandlerTest, singlePlayer_sameId_win) { .Times(1) .WillOnce(Return(playerObjectCounter)); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .Times(1) + .WillOnce(ReturnRef(globalVariables)); + auto terminationHandlerPtr = std::shared_ptr(new TerminationHandler(mockGridPtr, players)); TerminationConditionDefinition tcd; diff --git a/tests/src/Griddly/Core/GameProcessTest.cpp b/tests/src/Griddly/Core/GameProcessTest.cpp index 6492bb38c..0d6f04245 100644 --- a/tests/src/Griddly/Core/GameProcessTest.cpp +++ b/tests/src/Griddly/Core/GameProcessTest.cpp @@ -555,10 +555,12 @@ TEST(GameProcessTest, performActions) { uint32_t playerId = 1; + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getTickCount()) .WillOnce(Return(std::make_shared(0))); EXPECT_CALL(*mockGridPtr, getGlobalVariables()) - .WillOnce(Return(std::unordered_map>>{})); + .WillOnce(ReturnRef(globalVariables)); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, nullptr, mockGridPtr)); @@ -591,10 +593,12 @@ TEST(GameProcessTest, performActions) { TEST(GameProcessTest, performActionsDelayedReward) { auto mockGridPtr = std::shared_ptr(new MockGrid()); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getTickCount()) .WillOnce(Return(std::make_shared(0))); EXPECT_CALL(*mockGridPtr, getGlobalVariables()) - .WillOnce(Return(std::unordered_map>>{})); + .WillOnce(ReturnRef(globalVariables)); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, nullptr, mockGridPtr)); @@ -777,7 +781,7 @@ TEST(GameProcessTest, getState) { .WillOnce(Return(_V(10))); EXPECT_CALL(*mockGridPtr, getGlobalVariables()) - .WillRepeatedly(Return(globalVariables)); + .WillRepeatedly(ReturnRef(globalVariables)); auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, nullptr, mockGridPtr)); diff --git a/tests/src/Griddly/Core/GridTest.cpp b/tests/src/Griddly/Core/GridTest.cpp index 6f6a1dc43..b3f9e3755 100644 --- a/tests/src/Griddly/Core/GridTest.cpp +++ b/tests/src/Griddly/Core/GridTest.cpp @@ -42,7 +42,7 @@ TEST(GridTest, initializeAvatarObjectDefaultPlayer) { EXPECT_CALL(*mockObjectPtr, isPlayerAvatar()) .WillOnce(Return(true)); - grid->initObject("player_1_avatar"); + grid->initObject("player_1_avatar", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -64,7 +64,7 @@ TEST(GridTest, initializeAvatarObjectSpecificPlayer) { EXPECT_CALL(*mockObjectPtr, isPlayerAvatar()) .WillOnce(Return(true)); - grid->initObject("player_1_avatar"); + grid->initObject("player_1_avatar", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -82,7 +82,7 @@ TEST(GridTest, initializeObject) { grid->resetMap(123, 456); auto mockObjectPtr = mockObject("object_1"); - grid->initObject("object_1"); + grid->initObject("object_1", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -99,8 +99,8 @@ TEST(GridTest, initializeObjectPositionTwice) { auto mockObjectPtr = mockObject("object_1"); auto mockObjectPtr2 = mockObject("object_2"); - grid->initObject("object_1"); - grid->initObject("object_2"); + grid->initObject("object_1", {}); + grid->initObject("object_2", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -120,8 +120,8 @@ TEST(GridTest, initializeObjectPositionTwiceDifferentZ) { auto mockObjectPtr = mockObject("object1", 1, 0, 0); auto mockObjectPtr2 = mockObject("object2", 1, 0, 1); - grid->initObject("object1"); - grid->initObject("object2"); + grid->initObject("object1", {}); + grid->initObject("object2", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -139,7 +139,7 @@ TEST(GridTest, initializeObjectTwice) { grid->resetMap(123, 456); auto mockObjectPtr = mockObject("object"); - grid->initObject("object"); + grid->initObject("object", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -161,7 +161,7 @@ TEST(GridTest, removeObject) { auto objectLocation = glm::ivec2(1, 2); auto mockObjectPtr = mockObject("object", playerId, 0, 0, objectLocation); - grid->initObject("object"); + grid->initObject("object", {}); grid->addObject(objectLocation, mockObjectPtr); @@ -180,7 +180,7 @@ TEST(GridTest, removeObjectNotInitialized) { auto objectLocation = glm::ivec2{4, 4}; auto mockObjectPtr = mockObject("object", 1, 0, 0, objectLocation); - grid->initObject("object"); + grid->initObject("object", {}); ASSERT_EQ(grid->getObjects().size(), 0); ASSERT_EQ(grid->getUpdatedLocations(1).size(), 0); @@ -235,7 +235,7 @@ TEST(GridTest, performActionOnObjectWithNeutralPlayerId) { auto actionDestinationLocation = glm::ivec2(2, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -279,7 +279,7 @@ TEST(GridTest, performActionOnObjectWithDifferentPlayerId) { auto mockSourceObjectLocation = glm::ivec2(1, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -309,7 +309,7 @@ TEST(GridTest, performActionDestinationObjectNull) { auto actionDestinationLocation = glm::ivec2(2, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -358,12 +358,12 @@ TEST(GridTest, performActionCannotBePerformedOnDestinationObject) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); uint32_t mockDestinationObjectPlayerId = 2; auto mockDestinationObjectLocation = glm::ivec2(0, 1); auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); - grid->initObject("dstObject"); + grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); grid->addObject(mockDestinationObjectLocation, mockDestinationObjectPtr); @@ -416,12 +416,12 @@ TEST(GridTest, performActionCanBePerformedOnDestinationObject) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); uint32_t mockDestinationObjectPlayerId = 2; auto mockDestinationObjectLocation = glm::ivec2(0, 1); auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); - grid->initObject("dstObject"); + grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); grid->addObject(mockDestinationObjectLocation, mockDestinationObjectPtr); @@ -475,12 +475,12 @@ TEST(GridTest, performActionDelayed) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); - grid->initObject("srcObject"); + grid->initObject("srcObject", {}); uint32_t mockDestinationObjectPlayerId = 2; auto mockDestinationObjectLocation = glm::ivec2(0, 1); auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); - grid->initObject("dstObject"); + grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); grid->addObject(mockDestinationObjectLocation, mockDestinationObjectPtr); @@ -548,7 +548,7 @@ TEST(GridTest, objectCounters) { std::unordered_map>> objects; std::string objectName = "cat"; - grid->initObject("cat"); + grid->initObject("cat", {}); for (uint32_t p = 0; p < 10; p++) { for (uint32_t o = 0; o < 5; o++) { auto mockObject = std::shared_ptr(new MockObject()); @@ -594,7 +594,7 @@ TEST(GridTest, objectCountersEmpty) { auto grid = std::shared_ptr(new Grid()); grid->resetMap(123, 456); - grid->initObject("object"); + grid->initObject("object", {}); auto objectCounter = grid->getObjectCounter("object"); ASSERT_EQ(*objectCounter[0], 0); @@ -604,7 +604,7 @@ TEST(GridTest, runInitialActionsForObject) { auto grid = std::shared_ptr(new Grid()); grid->resetMap(123, 456); - grid->initObject("object"); + grid->initObject("object", {}); auto mockObjectPtr = mockObject("object"); auto mockActionPtr1 = std::shared_ptr(new MockAction()); auto mockActionPtr2 = std::shared_ptr(new MockAction()); diff --git a/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp b/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp index ec085d696..f17f04b1f 100644 --- a/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp +++ b/tests/src/Griddly/Core/LevelGenerator/MapReaderTest.cpp @@ -43,12 +43,16 @@ TEST(MapReaderTest, testLoadStringWithPlayerObjects) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectDefinitions()) .WillRepeatedly(Return(objectDefinitions)); - EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName), Eq(std::vector{}))) .Times(1); - EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName), Eq(std::vector{}))) .Times(1); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables) + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('W'))) .Times(8) .WillRepeatedly(ReturnRef(wallObjectName)); @@ -99,12 +103,16 @@ TEST(MapReaderTest, testLoadStringWithPlayerObjectsRandomWhitespace) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectDefinitions()) .WillRepeatedly(Return(objectDefinitions)); - EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName), Eq(std::vector{}))) .Times(1); - EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName), Eq(std::vector{}))) .Times(1); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables) + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('W'))) .Times(8) .WillRepeatedly(ReturnRef(wallObjectName)); @@ -155,12 +163,16 @@ TEST(MapReaderTest, testLoadStringNoSpaces) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectDefinitions()) .WillRepeatedly(Return(objectDefinitions)); - EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName), Eq(std::vector{}))) .Times(1); - EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName), Eq(std::vector{}))) .Times(1); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables) + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('W'))) .Times(8) .WillRepeatedly(ReturnRef(wallObjectName)); @@ -210,12 +222,16 @@ TEST(MapReaderTest, testLoadStringNoSpacesWithDots) { EXPECT_CALL(*mockObjectGeneratorPtr, getObjectDefinitions()) .WillRepeatedly(Return(objectDefinitions)); - EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(wallObjectName), Eq(std::vector{}))) .Times(1); - EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName))) + EXPECT_CALL(*mockGridPtr, initObject(Eq(avatarObjectName), Eq(std::vector{}))) .Times(1); + std::unordered_map>> globalVariables{}; + EXPECT_CALL(*mockGridPtr, getGlobalVariables) + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockObjectGeneratorPtr, getObjectNameFromMapChar(Eq('W'))) .Times(12) .WillRepeatedly(ReturnRef(wallObjectName)); diff --git a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp index 45efbb7f2..088d8a7f3 100644 --- a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp +++ b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp @@ -2,6 +2,7 @@ #include "Griddly/Core/Grid.hpp" #include "Griddly/Core/Observers/VectorObserver.hpp" #include "Mocks/Griddly/Core/MockGrid.hpp" +#include "ObserverRTSTestData.hpp" #include "ObserverTestData.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -24,7 +25,6 @@ void runVectorObserverTest(ObserverConfig observerConfig, std::vector expectedObservationStride, uint8_t* expectedData, bool trackAvatar) { - ObserverTestData testEnvironment = ObserverTestData(observerConfig, DiscreteOrientation(avatarDirection), trackAvatar); std::shared_ptr vectorObserver = std::shared_ptr(new VectorObserver(testEnvironment.mockGridPtr)); @@ -53,6 +53,38 @@ void runVectorObserverTest(ObserverConfig observerConfig, testEnvironment.verifyAndClearExpectations(); } +void runVectorObserverRTSTest(ObserverConfig observerConfig, + std::vector expectedObservationShape, + std::vector expectedObservationStride, + uint8_t* expectedData) { + auto mockGridPtr = std::shared_ptr(new MockGrid()); + + ObserverRTSTestData testEnvironment = ObserverRTSTestData(observerConfig); + + std::shared_ptr vectorObserver = std::shared_ptr(new VectorObserver(testEnvironment.mockGridPtr)); + + vectorObserver->init(observerConfig); + + auto resetObservation = vectorObserver->reset(); + + ASSERT_EQ(vectorObserver->getTileSize(), glm::ivec2(1, 1)); + ASSERT_EQ(vectorObserver->getShape(), expectedObservationShape); + ASSERT_EQ(vectorObserver->getStrides()[0], expectedObservationStride[0]); + ASSERT_EQ(vectorObserver->getStrides()[1], expectedObservationStride[1]); + + auto updateObservation = vectorObserver->update(); + + size_t dataLength = vectorObserver->getShape()[0] * vectorObserver->getShape()[1] * vectorObserver->getShape()[2]; + + auto resetObservationPointer = std::vector(resetObservation, resetObservation + dataLength); + auto updateObservationPointer = std::vector(updateObservation, updateObservation + dataLength); + + ASSERT_THAT(resetObservationPointer, ElementsAreArray(expectedData, dataLength)); + ASSERT_THAT(updateObservationPointer, ElementsAreArray(expectedData, dataLength)); + + testEnvironment.verifyAndClearExpectations(); +} + TEST(VectorObserverTest, defaultObserverConfig) { ObserverConfig config = { 5, @@ -450,4 +482,72 @@ TEST(VectorObserverTest, partialObserver_withOffset_trackAvatar_rotateWithAvatar runVectorObserverTest(config, Direction::LEFT, {4, 5, 3}, {1, 4, 20}, expectedData[0][0], true); } +TEST(VectorObserverTest, multiPlayer_Outline_Player1) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 1; + config.playerCount = 3; + + config.includePlayerId = true; + + uint8_t expectedData[5][5][8] = { + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); +} + +TEST(VectorObserverTest, multiPlayer_Outline_Player2) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 2; + config.playerCount = 3; + + config.includePlayerId = true; + + uint8_t expectedData[5][5][8] = { + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); +} + +TEST(VectorObserverTest, multiPlayer_Outline_Player3) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 3; + config.playerCount = 3; + + config.includePlayerId = true; + + uint8_t expectedData[5][5][8] = { + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); +} + +TEST(VectorObserverTest, multiPlayer_Outline_Global) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 0; + config.playerCount = 3; + + config.includePlayerId = true; + + uint8_t expectedData[5][5][8] = { + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, + {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); +} + } // namespace griddly \ No newline at end of file diff --git a/tests/src/Mocks/Griddly/Core/MockGrid.hpp b/tests/src/Mocks/Griddly/Core/MockGrid.hpp index ff278013c..0378fcd30 100644 --- a/tests/src/Mocks/Griddly/Core/MockGrid.hpp +++ b/tests/src/Mocks/Griddly/Core/MockGrid.hpp @@ -22,14 +22,14 @@ class MockGrid : public Grid { MOCK_METHOD(bool, updateLocation, (std::shared_ptr object, glm::ivec2 previousLocation, glm::ivec2 newLocation), ()); MOCK_METHOD(std::vector, performActions, (uint32_t playerId, std::vector> actions), ()); - MOCK_METHOD(void, initObject, (std::string), ()); + MOCK_METHOD(void, initObject, (std::string, std::vector), ()); MOCK_METHOD(void, addObject, (glm::ivec2 location, std::shared_ptr object, bool applyInitialActions), ()); MOCK_METHOD(bool, removeObject, (std::shared_ptr object), ()); MOCK_METHOD((std::unordered_map>), getObjectCounter, (std::string), ()); MOCK_METHOD(uint32_t, getUniqueObjectCount, (), (const)); - MOCK_METHOD((std::unordered_map>>), getGlobalVariables, (), (const)); + MOCK_METHOD((const std::unordered_map>>&), getGlobalVariables, (), (const)); MOCK_METHOD((const std::unordered_set>&), getObjects, (), ()); MOCK_METHOD(std::shared_ptr, getObject, (glm::ivec2 location), (const)); From 80f95b30bd1e19fbb3d5ee5508f5658ce3c44aca Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Feb 2021 10:37:43 +0000 Subject: [PATCH 07/34] player information in vector observer --- src/Griddly/Core/Observers/VectorObserver.cpp | 33 ++++++++++---- src/Griddly/Core/Observers/VectorObserver.hpp | 2 +- .../Core/Observers/VectorObserverTest.cpp | 45 ++++++++++--------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index 472fb1ecd..a9a911494 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -12,8 +12,6 @@ VectorObserver::~VectorObserver() {} void VectorObserver::init(ObserverConfig observerConfig) { Observer::init(observerConfig); - - isPartiallyObservable_ = avatarObject_ != nullptr; } ObserverType VectorObserver::getObserverType() const { @@ -36,7 +34,7 @@ void VectorObserver::resetShape() { // Always in order objects, player, orientation, variables. if (observerConfig_.includePlayerId) { - observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" + observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" } if (observerConfig_.includeOrientation) { @@ -51,6 +49,8 @@ void VectorObserver::resetShape() { observationStrides_ = {1, observationChannels_, observationChannels_ * gridWidth_}; observation_ = std::shared_ptr(new uint8_t[observationChannels_ * gridWidth_ * gridHeight_]{}); + + trackAvatar_ = avatarObject_ != nullptr; } uint8_t* VectorObserver::reset() { @@ -69,7 +69,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output } // Only put the *include* information of the first object - bool processTopLayer = false; + bool processTopLayer = true; for (auto& objectIt : grid_->getObjectsAt(objectLocation)) { auto object = objectIt.second; auto memPtrObject = memPtr + object->getObjectId(); @@ -77,7 +77,23 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output if (processTopLayer) { if (observerConfig_.includePlayerId) { - auto playerMemPtr = memPtr + uniqueObjectCount + object->getPlayerId(); + // if we are including the player ID, we always set player = 1 from the perspective of the agent being controlled. + // e.g if this is observer is owned by player 3 then objects owned by player 3 will be rendered as "player 1". + // This is so multi-agent games always see the agents they are controlling from first person perspective + uint32_t playerIdx = 0; + uint32_t objectPlayerId = object->getPlayerId(); + + if (objectPlayerId == 0 || observerConfig_.playerId == 0) { + playerIdx = objectPlayerId; + } else if (objectPlayerId < observerConfig_.playerId) { + playerIdx = objectPlayerId + 1; + } else if (objectPlayerId == observerConfig_.playerId) { + playerIdx = 1; + } else { + playerIdx = objectPlayerId; + } + + auto playerMemPtr = memPtr + uniqueObjectCount + playerIdx; *playerMemPtr = 1; } @@ -94,7 +110,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output case Direction::LEFT: directionIdx = 3; } - auto orientationMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + directionIdx; + auto orientationMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 1 + directionIdx; *orientationMemPtr = 1; } @@ -108,7 +124,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output if (objectVariableIt != grid_->getObjectVariableNames().end()) { uint32_t variableIdx = std::distance(grid_->getObjectVariableNames().begin(), grid_->getObjectVariableNames().begin()); - auto variableMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 4 + variableIdx; + auto variableMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 5 + variableIdx; *variableMemPtr = variableValue; } else { throw std::runtime_error("Available variable not defined."); @@ -122,7 +138,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output } uint8_t* VectorObserver::update() const { - if (isPartiallyObservable_) { + if (trackAvatar_) { auto avatarLocation = avatarObject_->getLocation(); auto avatarOrientation = avatarObject_->getObjectOrientation(); auto avatarDirection = avatarOrientation.getDirection(); @@ -190,7 +206,6 @@ uint8_t* VectorObserver::update() const { outy = 0; for (auto objy = pGrid.bottom; objy <= pGrid.top; objy++) { if (objx < gridBoundary_.x && objx >= 0 && objy < gridBoundary_.y && objy >= 0) { - // place a 1 in every object "slice" where that object appears renderLocation({objx, objy}, {outx, outy}); } outy++; diff --git a/src/Griddly/Core/Observers/VectorObserver.hpp b/src/Griddly/Core/Observers/VectorObserver.hpp index 6329825d2..9c225e710 100644 --- a/src/Griddly/Core/Observers/VectorObserver.hpp +++ b/src/Griddly/Core/Observers/VectorObserver.hpp @@ -24,7 +24,7 @@ class VectorObserver : public Observer { private: std::shared_ptr observation_; - bool isPartiallyObservable_; + bool trackAvatar_; uint32_t observationChannels_; }; diff --git a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp index 088d8a7f3..000639822 100644 --- a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp +++ b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp @@ -490,11 +490,11 @@ TEST(VectorObserverTest, multiPlayer_Outline_Player1) { config.includePlayerId = true; uint8_t expectedData[5][5][8] = { - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}}; runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); } @@ -507,11 +507,12 @@ TEST(VectorObserverTest, multiPlayer_Outline_Player2) { config.includePlayerId = true; uint8_t expectedData[5][5][8] = { - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}}; + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); } @@ -524,11 +525,11 @@ TEST(VectorObserverTest, multiPlayer_Outline_Player3) { config.includePlayerId = true; uint8_t expectedData[5][5][8] = { - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}}; runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); } @@ -539,14 +540,14 @@ TEST(VectorObserverTest, multiPlayer_Outline_Global) { config.playerCount = 3; config.includePlayerId = true; - + uint8_t expectedData[5][5][8] = { - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 0, 0, 1, 0, 0, 0}}, - {{1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 1, 0, 0, 0}}}; - + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0, 1, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1}, {0, 1, 0, 0, 0, 0, 0, 1}, {0, 0, 1, 0, 0, 0, 0, 1}, {0, 0, 0, 1, 1, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0}}}; + runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); } From 6a61a65f1000a1b9c476919e77794f357b0ba753 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Feb 2021 15:25:38 +0000 Subject: [PATCH 08/34] testing for vector observer config --- src/Griddly/Core/GDY/GDYFactory.cpp | 25 +++- src/Griddly/Core/GDY/GDYFactory.hpp | 6 + src/Griddly/Core/GameProcess.cpp | 2 + src/Griddly/Core/Observers/Observer.hpp | 2 +- src/Griddly/Core/Observers/VectorObserver.cpp | 4 +- tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp | 123 ++++++++++++++++++ 6 files changed, 156 insertions(+), 6 deletions(-) diff --git a/src/Griddly/Core/GDY/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index a94077916..35e018017 100644 --- a/src/Griddly/Core/GDY/GDYFactory.cpp +++ b/src/Griddly/Core/GDY/GDYFactory.cpp @@ -75,6 +75,7 @@ void GDYFactory::loadEnvironment(YAML::Node environment) { auto observerConfigNode = environment["Observers"]; if (observerConfigNode.IsDefined()) { + parseVectorObserverConfig(observerConfigNode["Vector"]); parseSpriteObserverConfig(observerConfigNode["Sprite2D"]); parseBlockObserverConfig(observerConfigNode["Block2D"]); parseIsometricSpriteObserverConfig(observerConfigNode["Isometric"]); @@ -117,6 +118,20 @@ void GDYFactory::parseSpriteObserverConfig(YAML::Node observerConfigNode) { } } +void GDYFactory::parseVectorObserverConfig(YAML::Node observerConfigNode) { + if (!observerConfigNode.IsDefined()) { + spdlog::debug("Using defaults for vector observer configuration."); + } + + auto includePlayerId = observerConfigNode["IncludePlayerId"].as(false); + auto includeRotation = observerConfigNode["IncludeRotation"].as(false); + auto includeVariables = observerConfigNode["IncludeVariables"].as(false); + + vectorObserverConfig_.includePlayerId = includePlayerId; + vectorObserverConfig_.includeRotation = includeRotation; + vectorObserverConfig_.includeVariables = includeVariables; +} + void GDYFactory::parseIsometricSpriteObserverConfig(YAML::Node observerConfigNode) { if (!observerConfigNode.IsDefined()) { spdlog::debug("Using defaults for isometric sprite observer configuration."); @@ -184,7 +199,7 @@ void GDYFactory::parsePlayerDefinition(YAML::Node playerNode) { if (avatarObjectNode.IsDefined()) { auto avatarObjectName = avatarObjectNode.as(); objectGenerator_->setAvatarObject(avatarObjectName); - + spdlog::debug("Actions will control the object with name={0}", avatarObjectName); avatarObject_ = avatarObjectName; @@ -581,7 +596,7 @@ void GDYFactory::loadActionInputsDefinition(std::string actionName, YAML::Node I inputDefinition.mapToGrid = mapToGrid; - if(!internal) { + if (!internal) { externalActionNames_.push_back(actionName); } @@ -748,7 +763,7 @@ std::shared_ptr GDYFactory::getTerminationGenerator() cons } std::shared_ptr GDYFactory::getLevelGenerator(uint32_t level) const { - if(level >= mapLevelGenerators_.size()) { + if (level >= mapLevelGenerators_.size()) { auto error = fmt::format("Level {0} does not exist. Please choose a level Id less than {1}", level, mapLevelGenerators_.size()); spdlog::error(error); throw std::invalid_argument(error); @@ -793,6 +808,10 @@ ObserverConfig GDYFactory::getBlockObserverConfig() const { return blockObserverConfig_; } +ObserverConfig GDYFactory::getVectorObserverConfig() const { + return vectorObserverConfig_; +} + std::unordered_map GDYFactory::getGlobalVariableDefinitions() const { return globalVariableDefinitions_; } diff --git a/src/Griddly/Core/GDY/GDYFactory.hpp b/src/Griddly/Core/GDY/GDYFactory.hpp index 52ab87c06..a1b08e3b6 100644 --- a/src/Griddly/Core/GDY/GDYFactory.hpp +++ b/src/Griddly/Core/GDY/GDYFactory.hpp @@ -52,6 +52,7 @@ class GDYFactory { virtual ObserverConfig getSpriteObserverConfig() const; virtual ObserverConfig getIsometricSpriteObserverConfig() const; virtual ObserverConfig getBlockObserverConfig() const; + virtual ObserverConfig getVectorObserverConfig() const; virtual std::unordered_map getGlobalVariableDefinitions() const; @@ -81,10 +82,14 @@ class GDYFactory { BehaviourCommandArguments singleOrListNodeToCommandArguments(YAML::Node singleOrList); void parseGlobalVariables(YAML::Node variablesNode); + void parseTerminationConditions(YAML::Node terminationNode); + void parseIsometricSpriteObserverConfig(YAML::Node observerConfigNode); void parseSpriteObserverConfig(YAML::Node observerConfigNode); void parseBlockObserverConfig(YAML::Node observerConfigNode); + void parseVectorObserverConfig(YAML::Node observerConfigNode); + glm::ivec2 parseTileSize(YAML::Node observerConfigNode); void parseBlockObserverDefinitions(std::string objectName, YAML::Node blockNode); @@ -115,6 +120,7 @@ class GDYFactory { ObserverConfig spriteObserverConfig_{}; ObserverConfig isometricSpriteObserverConfig_{}; ObserverConfig blockObserverConfig_{}; + ObserverConfig vectorObserverConfig_{}; ResourceConfig resourceConfig_; diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index f1743796b..004ff0470 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -167,6 +167,8 @@ ObserverConfig GameProcess::getObserverConfig(ObserverType observerType) const { return gdyFactory_->getSpriteObserverConfig(); case ObserverType::BLOCK_2D: return gdyFactory_->getBlockObserverConfig(); + case ObserverType::VECTOR: + return gdyFactory_->getVectorObserverConfig(); default: return ObserverConfig{}; } diff --git a/src/Griddly/Core/Observers/Observer.hpp b/src/Griddly/Core/Observers/Observer.hpp index 1d1a552de..6ee334d4a 100644 --- a/src/Griddly/Core/Observers/Observer.hpp +++ b/src/Griddly/Core/Observers/Observer.hpp @@ -21,7 +21,7 @@ struct ObserverConfig { // Config for VECTOR observers only bool includeVariables = false; - bool includeOrientation = false; + bool includeRotation = false; bool includePlayerId = false; }; diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index a9a911494..ba9e13434 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -37,7 +37,7 @@ void VectorObserver::resetShape() { observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" } - if (observerConfig_.includeOrientation) { + if (observerConfig_.includeRotation) { observationChannels_ += 4; } @@ -97,7 +97,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output *playerMemPtr = 1; } - if (observerConfig_.includeOrientation) { + if (observerConfig_.includeRotation) { uint32_t directionIdx = 0; switch (object->getObjectOrientation().getDirection()) { case Direction::UP: diff --git a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp index 9165a649e..8146dcce4 100644 --- a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp +++ b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp @@ -73,6 +73,129 @@ TEST(GDYFactoryTest, loadEnvironment) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObjectGeneratorPtr.get())); } + +TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_playerId) { + auto mockObjectGeneratorPtr = std::shared_ptr(new MockObjectGenerator()); + auto mockTerminationGeneratorPtr = std::shared_ptr(new MockTerminationGenerator()); + auto gdyFactory = std::shared_ptr(new GDYFactory(mockObjectGeneratorPtr, mockTerminationGeneratorPtr, {})); + auto yamlString = R"( +Environment: + Name: Test + Description: Test Description + Observers: + Vector: + IncludePlayerId: True +)"; + + auto environmentNode = loadFromStringAndGetNode(yamlString, "Environment"); + + gdyFactory->loadEnvironment(environmentNode); + + ASSERT_EQ(gdyFactory->getName(), "Test"); + ASSERT_EQ(gdyFactory->getNumLevels(), 0); + + auto config = gdyFactory->getVectorObserverConfig(); + + ASSERT_EQ(config.includePlayerId, true); + ASSERT_EQ(config.includeRotation, false); + ASSERT_EQ(config.includeVariables, false); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationGeneratorPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObjectGeneratorPtr.get())); +} + +TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_variables) { + auto mockObjectGeneratorPtr = std::shared_ptr(new MockObjectGenerator()); + auto mockTerminationGeneratorPtr = std::shared_ptr(new MockTerminationGenerator()); + auto gdyFactory = std::shared_ptr(new GDYFactory(mockObjectGeneratorPtr, mockTerminationGeneratorPtr, {})); + auto yamlString = R"( +Environment: + Name: Test + Description: Test Description + Observers: + Vector: + IncludeVariables: True +)"; + + auto environmentNode = loadFromStringAndGetNode(yamlString, "Environment"); + + gdyFactory->loadEnvironment(environmentNode); + + ASSERT_EQ(gdyFactory->getName(), "Test"); + ASSERT_EQ(gdyFactory->getNumLevels(), 0); + + auto config = gdyFactory->getVectorObserverConfig(); + + ASSERT_EQ(config.includePlayerId, false); + ASSERT_EQ(config.includeRotation, false); + ASSERT_EQ(config.includeVariables, true); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationGeneratorPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObjectGeneratorPtr.get())); +} + +TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_rotation) { + auto mockObjectGeneratorPtr = std::shared_ptr(new MockObjectGenerator()); + auto mockTerminationGeneratorPtr = std::shared_ptr(new MockTerminationGenerator()); + auto gdyFactory = std::shared_ptr(new GDYFactory(mockObjectGeneratorPtr, mockTerminationGeneratorPtr, {})); + auto yamlString = R"( +Environment: + Name: Test + Description: Test Description + Observers: + Vector: + IncludeRotation: True +)"; + + auto environmentNode = loadFromStringAndGetNode(yamlString, "Environment"); + + gdyFactory->loadEnvironment(environmentNode); + + ASSERT_EQ(gdyFactory->getName(), "Test"); + ASSERT_EQ(gdyFactory->getNumLevels(), 0); + + auto config = gdyFactory->getVectorObserverConfig(); + + ASSERT_EQ(config.includePlayerId, false); + ASSERT_EQ(config.includeRotation, true); + ASSERT_EQ(config.includeVariables, false); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationGeneratorPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObjectGeneratorPtr.get())); +} + +TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_playerId_rotation_variables) { + auto mockObjectGeneratorPtr = std::shared_ptr(new MockObjectGenerator()); + auto mockTerminationGeneratorPtr = std::shared_ptr(new MockTerminationGenerator()); + auto gdyFactory = std::shared_ptr(new GDYFactory(mockObjectGeneratorPtr, mockTerminationGeneratorPtr, {})); + auto yamlString = R"( +Environment: + Name: Test + Description: Test Description + Observers: + Vector: + IncludePlayerId: True + IncludeVariables: True + IncludeRotation: True +)"; + + auto environmentNode = loadFromStringAndGetNode(yamlString, "Environment"); + + gdyFactory->loadEnvironment(environmentNode); + + ASSERT_EQ(gdyFactory->getName(), "Test"); + ASSERT_EQ(gdyFactory->getNumLevels(), 0); + +auto config = gdyFactory->getVectorObserverConfig(); + + ASSERT_EQ(config.includePlayerId, true); + ASSERT_EQ(config.includeRotation, true); + ASSERT_EQ(config.includeVariables, true); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationGeneratorPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObjectGeneratorPtr.get())); +} + TEST(GDYFactoryTest, loadEnvironment_Observer) { auto mockObjectGeneratorPtr = std::shared_ptr(new MockObjectGenerator()); auto mockTerminationGeneratorPtr = std::shared_ptr(new MockTerminationGenerator()); From 66d2505e3bbf8f7fafe7114551b661f55dc8ddb6 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Feb 2021 15:38:29 +0000 Subject: [PATCH 09/34] update the yaml schema for vector observer options --- resources/games/RTS/GriddlyRTS.yaml | 3 ++ resources/games/RTS/Stratega/heal-or-die.yaml | 3 ++ .../games/RTS/Stratega/kill-the-king.yaml | 3 ++ resources/games/RTS/Stratega/push-mania.yaml | 3 ++ resources/gdy-schema.json | 35 ++++++++++++++++--- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/resources/games/RTS/GriddlyRTS.yaml b/resources/games/RTS/GriddlyRTS.yaml index 8d5db3309..2dc2f1876 100644 --- a/resources/games/RTS/GriddlyRTS.yaml +++ b/resources/games/RTS/GriddlyRTS.yaml @@ -11,6 +11,9 @@ Environment: BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png IsoTileHeight: 16 IsoTileDepth: 4 + Vector: + IncludePlayerId: true + IncludeVariables: true Variables: - Name: player_resources InitialValue: 0 diff --git a/resources/games/RTS/Stratega/heal-or-die.yaml b/resources/games/RTS/Stratega/heal-or-die.yaml index c99b1174f..ede044a43 100644 --- a/resources/games/RTS/Stratega/heal-or-die.yaml +++ b/resources/games/RTS/Stratega/heal-or-die.yaml @@ -14,6 +14,9 @@ Environment: BackgroundTile: stratega/plain.png IsoTileHeight: 35 IsoTileDepth: 0 + Vector: + IncludePlayerId: true + IncludeVariables: true Variables: - Name: unit_count InitialValue: 0 diff --git a/resources/games/RTS/Stratega/kill-the-king.yaml b/resources/games/RTS/Stratega/kill-the-king.yaml index 9746a2f88..61a5b96cf 100644 --- a/resources/games/RTS/Stratega/kill-the-king.yaml +++ b/resources/games/RTS/Stratega/kill-the-king.yaml @@ -13,6 +13,9 @@ Environment: BackgroundTile: stratega/plain.png IsoTileHeight: 35 IsoTileDepth: 0 + Vector: + IncludePlayerId: true + IncludeVariables: true Player: Count: 2 Termination: diff --git a/resources/games/RTS/Stratega/push-mania.yaml b/resources/games/RTS/Stratega/push-mania.yaml index c7c3cd790..838e71a39 100644 --- a/resources/games/RTS/Stratega/push-mania.yaml +++ b/resources/games/RTS/Stratega/push-mania.yaml @@ -13,6 +13,9 @@ Environment: BackgroundTile: stratega/plain.png IsoTileHeight: 35 IsoTileDepth: 0 + Vector: + IncludePlayerId: true + IncludeVariables: true Variables: - Name: unit_count InitialValue: 0 diff --git a/resources/gdy-schema.json b/resources/gdy-schema.json index 76aa72210..2979c7bf1 100644 --- a/resources/gdy-schema.json +++ b/resources/gdy-schema.json @@ -41,17 +41,44 @@ "description": "Default properties for observers", "additionalProperties": false, "properties": { + "Vector": { + "$id": "#/properties/Environment/properties/Observers/properties/Vector", + "type": "object", + "title": "Vector", + "description": "Configuration options for the vector renderer.", + "additionalProperties": false, + "properties": { + "IncludePlayerId": { + "$id": "#/properties/Environment/properties/Observers/properties/Vector/properties/IncludePlayerId", + "title": "Player Id", + "type": "boolean", + "description": "Includes the player id in the vector representation of each tile." + }, + "IncludeRotation": { + "$id": "#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeRotation", + "title": "Rotation", + "type": "boolean", + "description": "Includes the rotation of the object in the vector representation of each tile." + }, + "IncludeVariables": { + "$id": "#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeVariables", + "title": "Variables", + "type": "boolean", + "description": "Includes the value of variables in vector representation of each tile." + } + } + }, "Sprite2D": { "$id": "#/properties/Environment/properties/Observers/properties/Sprite2D", "type": "object", "title": "Sprite 2D", - "description": "Configuration options for sprite 2D renderer.", + "description": "Configuration options for the sprite 2D renderer.", "additionalProperties": false, "properties": { "TileSize": { "$id": "#/properties/Environment/properties/Observers/properties/Sprite2D/properties/TileSize", "title": "Tile Size", - "description": "Configuration options for sprite 2D renderer.", + "description": "Tile size to use for sprites.", "oneOf": [ { "type": "integer", @@ -82,13 +109,13 @@ "$id": "#/properties/Environment/properties//Observers/properties/Block2D", "type": "object", "title": "Block 2D", - "description": "Configuration options for block 2D renderer.", + "description": "Configuration options for the block 2D renderer.", "additionalProperties": false, "properties": { "TileSize": { "$id": "#/properties/Environment/properties/Observers/properties/Sprite2D/properties/TileSize", "title": "Tile Size", - "description": "Configuration options for sprite 2D renderer.", + "description": "Tile size to use for blocks.", "oneOf": [ { "type": "integer", From 17dac92059d3cf30f56e5bcf31ef6c2add0b51d9 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 16 Feb 2021 15:38:09 +0000 Subject: [PATCH 10/34] finally have impala self-play working with invalid action masking --- python/.gitignore | 4 + .../griddlyrts/griddly_rts_global.png | Bin 459 -> 895 bytes python/examples/griddlyrts/griddly_rts_p1.png | Bin 459 -> 895 bytes python/examples/griddlyrts/griddly_rts_p2.png | Bin 459 -> 895 bytes .../griddlyrts/play_griddlyrts_gym.py | 2 +- python/examples/rllib/rllib_RTS.py | 44 ++---- python/examples/rllib/rllib_single_agent.py | 9 +- python/examples/rllib/test_rts.yaml | 34 +++-- python/griddly/GymWrapper.py | 4 +- .../torch/conditional_masking_distribution.py | 25 +++- .../torch/global_average_pooling_agent.py | 12 +- .../util/rllib/torch/impala/__init__.py | 0 .../torch/impala/im_vtrace_torch_policy.py | 128 ++++++++++++++++++ .../griddly/util/rllib/torch/impala/impala.py | 14 ++ python/griddly/util/rllib/torch/mixins.py | 11 +- python/griddly/util/rllib/wrappers.py | 45 ++++++ python/griddly/util/vector_visualization.py | 22 +-- src/Griddly/Core/Observers/VectorObserver.cpp | 10 +- src/Griddly/Core/Observers/VectorObserver.hpp | 2 + 19 files changed, 283 insertions(+), 83 deletions(-) create mode 100644 python/griddly/util/rllib/torch/impala/__init__.py create mode 100644 python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py create mode 100644 python/griddly/util/rllib/torch/impala/impala.py diff --git a/python/.gitignore b/python/.gitignore index 4d4cae5b1..bfed1620f 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -137,5 +137,9 @@ dmypy.json # Cython debug symbols cython_debug/ +# Griddly bits griddly/libs/ griddly/resources/ + +# Hacky stuff +scratchpad/ diff --git a/python/examples/griddlyrts/griddly_rts_global.png b/python/examples/griddlyrts/griddly_rts_global.png index 65c71f90b3d5c9bcd2ecb8a3b84a2524de085eb6..92dffddaf8577bafab91b14df23ca3aa09c5e138 100644 GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXz`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D diff --git a/python/examples/griddlyrts/griddly_rts_p1.png b/python/examples/griddlyrts/griddly_rts_p1.png index 65c71f90b3d5c9bcd2ecb8a3b84a2524de085eb6..92dffddaf8577bafab91b14df23ca3aa09c5e138 100644 GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXz`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D diff --git a/python/examples/griddlyrts/griddly_rts_p2.png b/python/examples/griddlyrts/griddly_rts_p2.png index 65c71f90b3d5c9bcd2ecb8a3b84a2524de085eb6..92dffddaf8577bafab91b14df23ca3aa09c5e138 100644 GIT binary patch literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXz`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D diff --git a/python/examples/griddlyrts/play_griddlyrts_gym.py b/python/examples/griddlyrts/play_griddlyrts_gym.py index 13bfa3fc2..06cb7344c 100644 --- a/python/examples/griddlyrts/play_griddlyrts_gym.py +++ b/python/examples/griddlyrts/play_griddlyrts_gym.py @@ -10,7 +10,7 @@ wrapper = GymWrapperFactory() wrapper.build_gym_from_yaml("GriddlyRTS-Adv", - 'RTS/Stratega/heal-or-die.yaml', + 'RTS/GriddlyRTS.yaml', global_observer_type=gd.ObserverType.VECTOR, player_observer_type=gd.ObserverType.VECTOR, level=0) diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index d66582a88..e77930d52 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -3,17 +3,13 @@ import ray from ray import tune -from ray.rllib.agents.impala import ImpalaTrainer -from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, setup_mixins from ray.rllib.models import ModelCatalog -from ray.rllib.policy.torch_policy import LearningRateSchedule, EntropyCoeffSchedule from ray.tune.registry import register_env from griddly import gd from griddly.util.rllib import RLlibMultiAgentWrapper from griddly.util.rllib.torch import GAPAgent -from griddly.util.rllib.torch.mixins import InvalidActionMaskingPolicyMixin - +from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer if __name__ == '__main__': sep = os.pathsep @@ -26,37 +22,11 @@ register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) - - def setup_invalid_mask_mixin(policy, obs_space, action_space, config): - InvalidActionMaskingPolicyMixin.__init__(policy) - setup_mixins(policy, obs_space, action_space, config) - - - def get_policy_class(config): - if config['framework'] == 'torch': - return InvalidActionMaskingTorchPolicy - else: - raise NotImplementedError('Tensorflow not supported') - - - InvalidActionMaskingTorchPolicy = VTraceTorchPolicy.with_updates( - name='InvalidActionMaskingTorchPolicy', - before_init=setup_invalid_mask_mixin, - mixins=[ - LearningRateSchedule, - EntropyCoeffSchedule, - InvalidActionMaskingPolicyMixin - ]) - - - InvalidActionMaskingImpalaTrainer = ImpalaTrainer.with_updates(default_policy=InvalidActionMaskingTorchPolicy, - get_policy_class=get_policy_class) - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_rts.yaml') config = { 'framework': 'torch', - 'num_workers': 8, + 'num_workers': 11, 'num_envs_per_worker': 4, # Must be set to false to use the InvalidActionMaskingPolicyMixin @@ -68,20 +38,22 @@ def get_policy_class(config): }, 'env': env_name, 'env_config': { - # Tell the RLlib wrapper to use invalid action masking + # Tell the RLlib wrapper to use invalid action masking 'invalid_action_masking': True, + 'record_video_config': { + 'frequency': 10000 # number of rollouts + }, + 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 0, 'max_steps': 1000, }, - 'lr': tune.grid_search([0.0005]) } stop = { - # 'training_iteration': 100, - 'timesteps_total': 1000000, + 'timesteps_total': 2000000, } result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index a6f90196f..1967f7186 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -24,8 +24,13 @@ config = { 'framework': 'torch', - 'num_workers': 0, + 'num_workers': 8, 'num_envs_per_worker': 1, + + 'rollout_fragment_length': 10, + 'train_batch_size': 1, + "learner_queue_size": 1, + 'monitor': True, 'model': { 'custom_model': 'GAP', @@ -41,7 +46,7 @@ 'level': 6, 'max_steps': 1000, }, - 'lr': tune.grid_search([0.0005, 0.001, 0.003]) + 'lr': tune.grid_search([0.0005]) } stop = { diff --git a/python/examples/rllib/test_rts.yaml b/python/examples/rllib/test_rts.yaml index 8f3d4aec8..fea084f0c 100644 --- a/python/examples/rllib/test_rts.yaml +++ b/python/examples/rllib/test_rts.yaml @@ -4,13 +4,15 @@ Environment: Description: An RTS Game. There's aliens and stuff. Observers: Sprite2D: - TileSize: 16 + TileSize: 32 BackgroundTile: oryx/oryx_tiny_galaxy/tg_sliced/tg_world/tg_world_floor_panel_metal_a.png Isometric: TileSize: [32, 48] BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png IsoTileHeight: 16 IsoTileDepth: 4 + Vector: + IncludePlayerId: true Variables: - Name: player_resources InitialValue: 0 @@ -19,7 +21,8 @@ Environment: Count: 2 Termination: Win: - - eq: [_score, 100] # First player to 10 reward points + - eq: [player_resources, 20] # First to 20 resources + Levels: - | W W W W W W W W W W W W @@ -38,6 +41,7 @@ Actions: - Src: Object: harvester Commands: + - incr: player_resources - incr: resources - reward: 1 Dst: @@ -51,19 +55,19 @@ Actions: # Steal resources from other players - - Src: - Preconditions: - - neq: [ src._playerId, dst._playerId ] - - gt: [dst.resources, 0] - Object: harvester - Commands: - - incr: resources - - reward: 1 - Dst: - Object: harvester - Commands: - - decr: resources - - reward: -1 +# - Src: +# Preconditions: +# - neq: [ src._playerId, dst._playerId ] +# - gt: [dst.resources, 0] +# Object: harvester +# Commands: +# - incr: resources +# - reward: 1 +# Dst: +# Object: harvester +# Commands: +# - decr: resources +# - reward: -1 diff --git a/python/griddly/GymWrapper.py b/python/griddly/GymWrapper.py index a16fc1cb9..874ac3b39 100644 --- a/python/griddly/GymWrapper.py +++ b/python/griddly/GymWrapper.py @@ -49,6 +49,8 @@ def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType self._players = [] self.player_count = self.gdy.get_player_count() + self.object_names = self.gdy.get_object_names() + self._global_observer_type = global_observer_type self._player_observer_type = [] @@ -181,7 +183,7 @@ def initialize_spaces(self): self.observation_space = observation_space - self._vector2rgb = Vector2RGB(10, self._observation_shape[0]) + self._vector2rgb = Vector2RGB(10, len(self.object_names)) self.action_space = self._create_action_space() diff --git a/python/griddly/util/rllib/torch/conditional_masking_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_distribution.py index d8c8e8e6b..7644f9d4c 100644 --- a/python/griddly/util/rllib/torch/conditional_masking_distribution.py +++ b/python/griddly/util/rllib/torch/conditional_masking_distribution.py @@ -18,6 +18,10 @@ def __init__(self, dist_inputs, valid_action_trees, dist_class): self._inputs_split = dist_inputs.split(tuple(self._action_space_shape), dim=1) def _mask_and_sample(self, options, logits): + + if len(options) == 0: + print('boooo') + mask = torch.zeros([logits.shape[0]]) mask[options] = 1 @@ -25,27 +29,39 @@ def _mask_and_sample(self, options, logits): dist = Categorical(logits=logits) sampled = dist.sample() - return sampled, logits + return sampled, logits, mask def get_actions_and_mask(self): actions = torch.zeros([self._num_inputs, self._num_action_parts]) masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) + mask = torch.zeros([self._num_inputs, self._num_action_logits]) for i in range(self._num_inputs): - # just do nothing if we have no action tree, also no gradients are propagated because mask is 0 if len(self._valid_action_trees) >= 1: subtree = self._valid_action_trees[i] subtree_options = list(subtree.keys()) + + # In the case there are no available actions for the player + if len(subtree_options) == 0: + subtree = {0: {0: {0: [0]}}} + subtree_options = [0] + mask_offset = 0 for a in range(self._num_action_parts): dist_part = self._inputs_split[a] - sampled, masked_logits_part = self._mask_and_sample(subtree_options, dist_part[i]) + sampled, masked_logits_part, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) # Set the action and the mask for each part of the action actions[i, a] = sampled masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part + mask[i, mask_offset:mask_offset + self._action_space_shape[a]] = mask_part + + if mask_part.sum() == 0: + raise RuntimeError('mask calculated incorrectly') + + mask_offset += self._action_space_shape[a] if isinstance(subtree, dict): subtree = subtree[int(sampled)] @@ -55,4 +71,5 @@ def get_actions_and_mask(self): # Leaf nodes with action_id list subtree_options = subtree - return actions, masked_logits + + return actions, masked_logits, mask diff --git a/python/griddly/util/rllib/torch/global_average_pooling_agent.py b/python/griddly/util/rllib/torch/global_average_pooling_agent.py index 4dcdf179a..a8ad58dd6 100644 --- a/python/griddly/util/rllib/torch/global_average_pooling_agent.py +++ b/python/griddly/util/rllib/torch/global_average_pooling_agent.py @@ -4,8 +4,8 @@ def layer_init(layer, std=np.sqrt(2), bias_const=0.0): - nn.init.orthogonal_(layer.weight, std) - nn.init.constant_(layer.bias, bias_const) + # nn.init.orthogonal_(layer.weight, std) + # nn.init.constant_(layer.bias, bias_const) return layer @@ -48,7 +48,9 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): layer_init(nn.Conv2d(64, 64, 3, padding=1)), nn.ReLU(), GlobalAvePool(2048), - layer_init(nn.Linear(2048, 512)), + layer_init(nn.Linear(2048, 1024)), + nn.ReLU(), + layer_init(nn.Linear(1024, 512)), nn.ReLU(), layer_init(nn.Linear(512, 512)) ) @@ -63,10 +65,6 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): layer_init(nn.Linear(512, 1), std=0.01) ) - self.view_requirements = { - - } - def forward(self, input_dict, state, seq_lens): obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) network_output = self.network(obs_transformed) diff --git a/python/griddly/util/rllib/torch/impala/__init__.py b/python/griddly/util/rllib/torch/impala/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py new file mode 100644 index 000000000..43c71f704 --- /dev/null +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py @@ -0,0 +1,128 @@ +import logging + +import gym +import numpy as np +import ray +from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major, VTraceLoss +from ray.rllib.models.torch.torch_action_dist import TorchCategorical +from ray.rllib.policy.sample_batch import SampleBatch +from ray.rllib.policy.torch_policy import LearningRateSchedule, \ + EntropyCoeffSchedule +from ray.rllib.utils.framework import try_import_torch +from ray.rllib.utils.torch_ops import sequence_mask + +from griddly.util.rllib.torch.mixins import InvalidActionMaskingPolicyMixin + +torch, nn = try_import_torch() + +logger = logging.getLogger(__name__) + + +def build_invalid_masking_vtrace_loss(policy, model, dist_class, train_batch): + model_out, _ = model.from_batch(train_batch) + + if isinstance(policy.action_space, gym.spaces.Discrete): + is_multidiscrete = False + output_hidden_shape = [policy.action_space.n] + elif isinstance(policy.action_space, gym.spaces.MultiDiscrete): + is_multidiscrete = True + output_hidden_shape = policy.action_space.nvec.astype(np.int32) + else: + is_multidiscrete = False + output_hidden_shape = 1 + + def _make_time_major(*args, **kw): + return make_time_major(policy, train_batch.get("seq_lens"), *args, + **kw) + + actions = train_batch[SampleBatch.ACTIONS] + dones = train_batch[SampleBatch.DONES] + rewards = train_batch[SampleBatch.REWARDS] + behaviour_action_logp = train_batch[SampleBatch.ACTION_LOGP] + behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS] + + # apply mask so target distribution is masked in the same way as the workers + # However instead of masking to very small negative number, just make it extremely unlikely + valid_action_mask = torch.tensor(train_batch['valid_action_mask']) + + if 'seq_lens' in train_batch: + max_seq_len = policy.config['rollout_fragment_length'] + mask_orig = sequence_mask(train_batch["seq_lens"], max_seq_len) + mask = torch.reshape(mask_orig, [-1]) + else: + mask = torch.ones_like(rewards) + + # valid actions masks should be all ones where values are masked out to avoid nan party. + valid_action_mask[torch.where(mask == False)] = 1 + + model_out += torch.log(valid_action_mask) + action_dist = dist_class(model_out, model) + + if isinstance(output_hidden_shape, (list, tuple, np.ndarray)): + unpacked_behaviour_logits = torch.split( + behaviour_logits, list(output_hidden_shape), dim=1) + unpacked_outputs = torch.split( + model_out, list(output_hidden_shape), dim=1) + else: + unpacked_behaviour_logits = torch.chunk( + behaviour_logits, output_hidden_shape, dim=1) + unpacked_outputs = torch.chunk(model_out, output_hidden_shape, dim=1) + values = model.value_function() + + # Prepare actions for loss. + loss_actions = actions if is_multidiscrete else torch.unsqueeze( + actions, dim=1) + + # Inputs are reshaped from [B * T] => [T - 1, B] for V-trace calc. + policy.loss = VTraceLoss( + actions=_make_time_major(loss_actions, drop_last=True), + actions_logp=_make_time_major( + action_dist.logp(actions), drop_last=True), + actions_entropy=_make_time_major( + action_dist.entropy(), drop_last=True), + dones=_make_time_major(dones, drop_last=True), + behaviour_action_logp=_make_time_major( + behaviour_action_logp, drop_last=True), + behaviour_logits=_make_time_major( + unpacked_behaviour_logits, drop_last=True), + target_logits=_make_time_major(unpacked_outputs, drop_last=True), + discount=policy.config["gamma"], + rewards=_make_time_major(rewards, drop_last=True), + values=_make_time_major(values, drop_last=True), + bootstrap_value=_make_time_major(values)[-1], + dist_class=TorchCategorical if is_multidiscrete else dist_class, + model=model, + valid_mask=_make_time_major(mask, drop_last=True), + config=policy.config, + vf_loss_coeff=policy.config["vf_loss_coeff"], + entropy_coeff=policy.entropy_coeff, + clip_rho_threshold=policy.config["vtrace_clip_rho_threshold"], + clip_pg_rho_threshold=policy.config["vtrace_clip_pg_rho_threshold"]) + + return policy.loss.total_loss + + +def setup_mixins(policy, obs_space, action_space, config): + InvalidActionMaskingPolicyMixin.__init__(policy) + EntropyCoeffSchedule.__init__(policy, config["entropy_coeff"], + config["entropy_coeff_schedule"]) + LearningRateSchedule.__init__(policy, config["lr"], config["lr_schedule"]) + + +def postprocess_episode(policy, sample_batch, other_agent_batches, episode): + if 'valid_action_mask' not in sample_batch: + sample_batch['valid_action_mask'] = np.ones_like(sample_batch['action_dist_inputs']) + else: + if sample_batch['valid_action_mask'].sum() == 0: + raise RuntimeError('empty action mask') + return sample_batch + + +InvalidMaskingVTraceTorchPolicy = VTraceTorchPolicy.with_updates( + name="InvalidMaskingVTraceTorchPolicy", + loss_fn=build_invalid_masking_vtrace_loss, + get_default_config=lambda: ray.rllib.agents.impala.impala.DEFAULT_CONFIG, + before_init=setup_mixins, + postprocess_fn=postprocess_episode, + mixins=[LearningRateSchedule, EntropyCoeffSchedule, InvalidActionMaskingPolicyMixin] +) diff --git a/python/griddly/util/rllib/torch/impala/impala.py b/python/griddly/util/rllib/torch/impala/impala.py new file mode 100644 index 000000000..a315f8a60 --- /dev/null +++ b/python/griddly/util/rllib/torch/impala/impala.py @@ -0,0 +1,14 @@ +from ray.rllib.agents.impala import ImpalaTrainer + +from griddly.util.rllib.torch.impala.im_vtrace_torch_policy import InvalidMaskingVTraceTorchPolicy + + +def get_policy_class(config): + if config['framework'] == 'torch': + return InvalidMaskingVTraceTorchPolicy + else: + raise NotImplementedError('Tensorflow not supported') + + +InvalidActionMaskingImpalaTrainer = ImpalaTrainer.with_updates(default_policy=InvalidMaskingVTraceTorchPolicy, + get_policy_class=get_policy_class) \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/mixins.py b/python/griddly/util/rllib/torch/mixins.py index 77e1e830c..737274e82 100644 --- a/python/griddly/util/rllib/torch/mixins.py +++ b/python/griddly/util/rllib/torch/mixins.py @@ -15,7 +15,10 @@ class InvalidActionMaskingPolicyMixin: - + """ + The info_batch contains the valid action trees. compute_actions is the only part of rllib that can has access to the + info_batch, therefore we have to override it to explore/exploit valid actions. + """ @override(Policy) def compute_actions( @@ -68,10 +71,10 @@ def compute_actions( if 'valid_action_tree' in info: valid_action_trees.append(info['valid_action_tree']) else: - valid_action_trees.append({0:{0:{0:[0]}}}) + valid_action_trees.append({0: {0: {0: [0]}}}) exploration = TorchConditionalMaskingExploration(dist_inputs, valid_action_trees, self.dist_class) - actions, masked_dist_actions = exploration.get_actions_and_mask() + actions, masked_dist_actions, mask = exploration.get_actions_and_mask() masked_action_dist = self.dist_class(masked_dist_actions, self.model) @@ -83,6 +86,8 @@ def compute_actions( extra_fetches = self.extra_action_out(input_dict, state_batches, self.model, masked_action_dist) + extra_fetches['valid_action_mask'] = mask + # Action-dist inputs. if dist_inputs is not None: extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_dist_actions diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index 3138949d5..6ef6074dc 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -1,4 +1,6 @@ +from uuid import uuid1 from collections import defaultdict +from enum import Enum from typing import Tuple import gym @@ -9,6 +11,13 @@ from griddly import GymWrapper import numpy as np +from griddly.RenderTools import VideoRecorder + +class RecordingState(Enum): + NOT_RECORDING = 1 + WAITING_FOR_EPISODE_START = 2 + BEFORE_RECORDING = 3 + RECORDING = 4 class RLlibWrapper(GymWrapper): """ @@ -47,9 +56,16 @@ def __init__(self, env_config): super().__init__(**env_config) self._invalid_action_masking = env_config.get('invalid_action_masking', False) + self._record_video_config = env_config.get('record_video_config', None) super().reset() + if self._record_video_config is not None: + self._recording_state = RecordingState.BEFORE_RECORDING + self._env_steps = 0 + self._record_frequency = self._record_video_config.get('frequency', 1000) + + self.set_transform() def _transform_obs_space(self, observation_space): @@ -90,6 +106,27 @@ def _transform(self, observation): return transformed_obs + def _after_step(self, observation, reward, done, info): + if self._recording_state is RecordingState.NOT_RECORDING and self._env_steps % self._record_frequency == 0: + self._recording_state = RecordingState.WAITING_FOR_EPISODE_START + + if self._recording_state == RecordingState.BEFORE_RECORDING: + global_obs = self.render(observer='global', mode='rgb_array') + self._global_recorder = VideoRecorder() + self._global_recorder.start(f'global_video_{uuid1()}_{self._env_steps}.mp4', global_obs.shape) + self._recording_state = RecordingState.RECORDING + + if self._recording_state == RecordingState.RECORDING: + global_obs = self.render(observer='global', mode='rgb_array') + self._global_recorder.add_frame(global_obs) + if done: + self._recording_state = RecordingState.NOT_RECORDING + self._global_recorder.close() + + if self._recording_state == RecordingState.WAITING_FOR_EPISODE_START: + if done: + self._recording_state = RecordingState.BEFORE_RECORDING + def set_transform(self): """ Create the transform for rllib based on the observation space @@ -108,6 +145,14 @@ def reset(self, **kwargs): def step(self, action): observation, reward, done, info = super().step(action) + + self._after_step(observation, reward, done, info) + + if reward == 0.0: + reward = -0.1 + + self._env_steps += 1 + return self._transform(observation), reward, done, info def render(self, mode='human', observer=0): diff --git a/python/griddly/util/vector_visualization.py b/python/griddly/util/vector_visualization.py index 254e4bd82..817d3c2d1 100644 --- a/python/griddly/util/vector_visualization.py +++ b/python/griddly/util/vector_visualization.py @@ -1,13 +1,16 @@ import numpy as np import colorsys + class Vector2RGB(): - def __init__(self, scale, observation_channels): + def __init__(self, scale, object_channels): self._vector_observer_scale = scale + self._object_channels = object_channels + # Create a colour palette for rendering vector observers - HSV_tuples = [(x * 1.0 / (observation_channels + 1), 1.0, 1.0) for x in range(observation_channels + 1)] + HSV_tuples = [(x * 1.0 / (object_channels + 1), 1.0, 1.0) for x in range(object_channels + 1)] vector_rgb = [] for hsv in HSV_tuples: @@ -15,19 +18,20 @@ def __init__(self, scale, observation_channels): self._vector_rgb_palette = (np.array(vector_rgb) * 255).astype('uint8') - def convert(self, observation): - # add extra dimension so argmax does not get confused by 0 index and empty space - palette_buffer = np.ones([observation.shape[0] + 1, *observation.shape[1:]]) * 0.5 - palette_buffer[1:] = observation + # Add extra dimension so argmax does not get confused by 0 index and empty space + palette_buffer = np.ones([self._object_channels + 1, *observation.shape[1:]]) * 0.5 + + # Only consider the object type when rendering + palette_buffer[1:] = observation[:self._object_channels, :, :] - # convert to RGB pallette + # Convert to RGB pallette vector_pallette = np.argmax(palette_buffer, axis=0).swapaxes(0, 1) buffer = self._vector_rgb_palette[vector_pallette] - # make the observation much bigger by repeating pixels + # Make the observation much bigger by repeating (this is horribly expensive) return buffer \ .repeat(self._vector_observer_scale, 0) \ .repeat(self._vector_observer_scale, 1) \ - .swapaxes(0, 2) \ No newline at end of file + .swapaxes(0, 2) diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index ba9e13434..8d1aca11f 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -38,10 +38,12 @@ void VectorObserver::resetShape() { } if (observerConfig_.includeRotation) { + channelsBeforeRotation_ = observationChannels_; observationChannels_ += 4; } if (observerConfig_.includeVariables) { + channelsBeforeVariables_ = observationChannels_; observationChannels_ += grid_->getObjectVariableNames().size(); } @@ -110,7 +112,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output case Direction::LEFT: directionIdx = 3; } - auto orientationMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 1 + directionIdx; + auto orientationMemPtr = memPtr + channelsBeforeRotation_ + directionIdx; *orientationMemPtr = 1; } @@ -124,11 +126,9 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output if (objectVariableIt != grid_->getObjectVariableNames().end()) { uint32_t variableIdx = std::distance(grid_->getObjectVariableNames().begin(), grid_->getObjectVariableNames().begin()); - auto variableMemPtr = memPtr + uniqueObjectCount + observerConfig_.playerCount + 5 + variableIdx; + auto variableMemPtr = memPtr + channelsBeforeVariables_ + variableIdx; *variableMemPtr = variableValue; - } else { - throw std::runtime_error("Available variable not defined."); - } + } } } diff --git a/src/Griddly/Core/Observers/VectorObserver.hpp b/src/Griddly/Core/Observers/VectorObserver.hpp index 9c225e710..0a65ded62 100644 --- a/src/Griddly/Core/Observers/VectorObserver.hpp +++ b/src/Griddly/Core/Observers/VectorObserver.hpp @@ -26,6 +26,8 @@ class VectorObserver : public Observer { std::shared_ptr observation_; bool trackAvatar_; uint32_t observationChannels_; + uint32_t channelsBeforeRotation_; + uint32_t channelsBeforeVariables_; }; } // namespace griddly \ No newline at end of file From 6f905e0e0e326e39df70bcfd58a307969e2067f4 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Thu, 18 Feb 2021 18:13:04 +0000 Subject: [PATCH 11/34] fixing big issue with reward allocation in multi-agent situations --- bindings/python.cpp | 11 +- bindings/wrapper/GDYWrapper.cpp | 4 - bindings/wrapper/GameWrapper.cpp | 17 +- bindings/wrapper/StepPlayerWrapper.cpp | 13 +- python/examples/rllib/rllib_RTS.py | 13 +- .../rllib/rllib_multiagent_grouped.py | 86 ++++++ python/examples/rllib/rllib_single_agent.py | 11 +- python/examples/rllib/test_ma_grouped.yaml | 133 +++++++++ python/griddly/GymWrapper.py | 5 +- python/griddly/util/rllib/wrappers.py | 92 ++++--- src/Griddly/Core/GDY/Objects/Object.cpp | 143 +++++----- src/Griddly/Core/GDY/Objects/Object.hpp | 7 +- .../Core/GDY/Objects/ObjectGenerator.cpp | 13 +- .../Core/GDY/Objects/ObjectGenerator.hpp | 4 - src/Griddly/Core/GameProcess.hpp | 7 +- src/Griddly/Core/Grid.cpp | 86 ++++-- src/Griddly/Core/Grid.hpp | 29 +- src/Griddly/Core/Observers/VectorObserver.cpp | 17 +- src/Griddly/Core/Observers/VectorObserver.hpp | 1 + src/Griddly/Core/Players/Player.cpp | 4 +- src/Griddly/Core/TurnBasedGameProcess.cpp | 29 +- src/Griddly/Core/TurnBasedGameProcess.hpp | 2 +- src/Griddly/Core/Util/util.hpp | 6 + .../Griddly/Core/GDY/Objects/ObjectTest.cpp | 253 +++++++----------- tests/src/Griddly/Core/GameProcessTest.cpp | 24 +- tests/src/Griddly/Core/GridTest.cpp | 79 +++--- .../Core/Observers/ObserverRTSTestData.hpp | 55 +++- .../Core/Observers/ObserverTestData.hpp | 18 +- .../Core/Observers/VectorObserverTest.cpp | 36 +++ tests/src/Griddly/Core/Players/PlayerTest.cpp | 12 +- tests/src/Griddly/Core/TestUtils/common.hpp | 3 +- .../Griddly/Core/GDY/Objects/MockObject.hpp | 7 +- tests/src/Mocks/Griddly/Core/MockGrid.hpp | 8 +- 33 files changed, 751 insertions(+), 477 deletions(-) create mode 100644 python/examples/rllib/rllib_multiagent_grouped.py create mode 100644 python/examples/rllib/test_ma_grouped.yaml diff --git a/bindings/python.cpp b/bindings/python.cpp index c4f5b7215..dfbc8917f 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -35,9 +35,6 @@ PYBIND11_MODULE(python_griddly, m) { gdy.def("get_avatar_object", &Py_GDYWrapper::getAvatarObject); gdy.def("create_game", &Py_GDYWrapper::createGame); - // Get list of objects in the order of their assigned ID - gdy.def("get_object_names", &Py_GDYWrapper::getObjectNames); - py::class_> game_process(m, "GameProcess"); @@ -59,8 +56,6 @@ PYBIND11_MODULE(python_griddly, m) { game_process.def("get_available_actions", &Py_GameWrapper::getAvailableActionNames); game_process.def("get_available_action_ids", &Py_GameWrapper::getAvailableActionIds); - - // Width and height of the game grid game_process.def("get_width", &Py_GameWrapper::getWidth); game_process.def("get_height", &Py_GameWrapper::getHeight); @@ -81,6 +76,12 @@ PYBIND11_MODULE(python_griddly, m) { // Get a specific variable value game_process.def("get_global_variable", &Py_GameWrapper::getGlobalVariables); + // Get list of possible object names, ordered by ID + game_process.def("get_object_names", &Py_GameWrapper::getObjectNames); + + // Get list of possible variable names, ordered by ID + game_process.def("get_object_variable_names", &Py_GameWrapper::getObjectVariableNames); + // Get a list of the events that have happened in the game up to this point game_process.def("get_history", &Py_GameWrapper::getHistory, py::arg("purge")=true); diff --git a/bindings/wrapper/GDYWrapper.cpp b/bindings/wrapper/GDYWrapper.cpp index 0e6a71bb1..2be9fc232 100644 --- a/bindings/wrapper/GDYWrapper.cpp +++ b/bindings/wrapper/GDYWrapper.cpp @@ -35,10 +35,6 @@ class Py_GDYWrapper { return gdyFactory_->getExternalActionNames(); } - std::vector getObjectNames() const { - return gdyFactory_->getObjectGenerator()->getObjectNames(); - } - py::dict getActionInputMappings() const { auto actionInputsDefinitions = gdyFactory_->getActionInputsDefinitions(); py::dict py_actionInputsDefinitions; diff --git a/bindings/wrapper/GameWrapper.cpp b/bindings/wrapper/GameWrapper.cpp index 994159582..2b9510f20 100644 --- a/bindings/wrapper/GameWrapper.cpp +++ b/bindings/wrapper/GameWrapper.cpp @@ -276,13 +276,18 @@ class Py_GameWrapper { std::vector py_events; if (history.size() > 0) { - for (auto historyEvent : history) { + for (const auto& historyEvent : history) { py::dict py_event; + py::dict rewards; + for (auto& reward: historyEvent.rewards) { + rewards[py::cast(reward.first)] = reward.second; + } + py_event["PlayerId"] = historyEvent.playerId; py_event["ActionName"] = historyEvent.actionName; py_event["Tick"] = historyEvent.tick; - py_event["Reward"] = historyEvent.reward; + py_event["Rewards"] = rewards; py_event["Delay"] = historyEvent.delay; py_event["SourceObjectName"] = historyEvent.sourceObjectName; @@ -305,6 +310,14 @@ class Py_GameWrapper { return py_events; } + std::vector getObjectNames() { + return gameProcess_->getGrid()->getObjectNames(); + } + + std::vector getObjectVariableNames() { + return gameProcess_->getGrid()->getObjectVariableNames(); + } + private: const std::shared_ptr gameProcess_; const std::shared_ptr gdyFactory_; diff --git a/bindings/wrapper/StepPlayerWrapper.cpp b/bindings/wrapper/StepPlayerWrapper.cpp index 9e237f70d..154144301 100644 --- a/bindings/wrapper/StepPlayerWrapper.cpp +++ b/bindings/wrapper/StepPlayerWrapper.cpp @@ -125,18 +125,9 @@ class Py_StepPlayerWrapper { const std::shared_ptr gameProcess_; py::tuple performActions(std::vector> actions, bool updateTicks) { - ActionResult actionResult; - - actionResult = player_->performActions(actions, updateTicks); - - int totalRewards = 0; - for (auto &r : actionResult.rewards) { - totalRewards += r; - } - + auto actionResult = player_->performActions(actions, updateTicks); auto info = buildInfo(actionResult); - - return py::make_tuple(totalRewards, actionResult.terminated, info); + return py::make_tuple(actionResult.reward, actionResult.terminated, info); } py::dict buildInfo(ActionResult actionResult) { diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index e77930d52..be34624a7 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -15,20 +15,23 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + ray.init(num_gpus=1, local_mode=True) env_name = 'ray-griddly-rts-env' register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_rts.yaml') + #test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_rts.yaml') config = { 'framework': 'torch', - 'num_workers': 11, + 'num_workers': 8, 'num_envs_per_worker': 4, + 'rollout_fragment_length': 10, + 'train_batch_size': 200, + # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, 'monitor': True, @@ -45,8 +48,8 @@ 'frequency': 10000 # number of rollouts }, - 'yaml_file': test_path, - 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'yaml_file': 'RTS/GriddlyRTS.yaml', + 'global_observer_type': gd.ObserverType.ISOMETRIC, 'level': 0, 'max_steps': 1000, }, diff --git a/python/examples/rllib/rllib_multiagent_grouped.py b/python/examples/rllib/rllib_multiagent_grouped.py new file mode 100644 index 000000000..8aa8c5094 --- /dev/null +++ b/python/examples/rllib/rllib_multiagent_grouped.py @@ -0,0 +1,86 @@ +import os +import sys + +import numpy as np +import ray +from gym.spaces import Box, MultiDiscrete +from ray import tune +from ray.rllib.agents.impala import ImpalaTrainer +from ray.rllib.agents.ppo import PPOTrainer +from ray.rllib.models import ModelCatalog +from ray.tune.registry import register_env + +from griddly import gd +from griddly.util.rllib import RLlibMultiAgentWrapper +from griddly.util.rllib.torch import GAPAgent + +if __name__ == '__main__': + sep = os.pathsep + os.environ['PYTHONPATH'] = sep.join(sys.path) + + ray.init(num_gpus=1, local_mode=True) + + test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_ma_grouped.yaml') + + env_name = 'ray-ma-grouped-env' + + env_config = { + # in the griddly environment we set a variable to let the training environment + # know if that player is no longer active + 'player_done_variable': 'player_done', + + # 'record_video_config': { + # 'frequency': 10000 # number of rollouts + # }, + + 'yaml_file': test_path, + 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'level': 0, + 'max_steps': 1000, + } + + + def _env_creator(env_config): + agent_groups = { + "groups": { + "group1": [1, 2, 3], + "group2": [4, 5, 6], + "group3": [7, 8, 9], + "group4": [10, 11, 12], + } + } + + obs_space = Box(0, 255, shape=(5, 5, 3), dtype=np.float) + act_space = MultiDiscrete([2, 5]) + return RLlibMultiAgentWrapper(env_config).with_agent_groups( + agent_groups, + obs_space=obs_space, + act_space=act_space + ) + + register_env(env_name, _env_creator) + + ModelCatalog.register_custom_model('GAP', GAPAgent) + + config = { + 'framework': 'torch', + + 'train_batch_size': 256, + 'rollout_fragment_length': 10, + 'lr': 0.0003, + + 'num_workers': 8, + 'num_envs_per_worker': 1, + 'model': { + 'custom_model': 'GAP', + 'custom_model_config': {} + }, + 'env': env_name, + 'env_config': env_config + } + + stop = { + 'timesteps_total': 2000000, + } + + result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 1967f7186..7668d700f 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -4,6 +4,7 @@ import ray from ray import tune from ray.rllib.agents.impala import ImpalaTrainer +from ray.rllib.agents.ppo import PPOTrainer from ray.rllib.models import ModelCatalog from ray.tune.registry import register_env @@ -15,7 +16,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(local_mode=True) + ray.init(num_gpus=1, local_mode=True) env_name = "ray-griddly-env" @@ -24,13 +25,9 @@ config = { 'framework': 'torch', - 'num_workers': 8, + 'num_workers': 1, 'num_envs_per_worker': 1, - 'rollout_fragment_length': 10, - 'train_batch_size': 1, - "learner_queue_size": 1, - 'monitor': True, 'model': { 'custom_model': 'GAP', @@ -54,4 +51,4 @@ "timesteps_total": 10000000, } - result = tune.run(ImpalaTrainer, config=config, stop=stop) + result = tune.run(PPOTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/test_ma_grouped.yaml b/python/examples/rllib/test_ma_grouped.yaml new file mode 100644 index 000000000..0c8d5e3c6 --- /dev/null +++ b/python/examples/rllib/test_ma_grouped.yaml @@ -0,0 +1,133 @@ +Version: "0.1" +Environment: + Name: Robot Battle + Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! + Observers: + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: player_done_count + InitialValue: 0 + Player: + Count: 12 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 5 + Width: 5 + OffsetX: 0 + OffsetY: 0 + AvatarObject: fighter + Termination: + Win: + - eq: [player_done_count, 11] + + Levels: + - | + W W W W W W W W W + W . . f2 . f12 . . W + W . . . . . . . W + W f1 . f3 . f10 . f11 W + W . . . . . . . W + W . . . . . . . W + W f4 . f5 . f7 . f8 W + W . . . . . . . W + W . . f6 . f9 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f12 . . W + W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W + W . . f6 . . . . . . . . . . . . . . f9 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + - Name: attack + Behaviours: + - Src: + Object: fighter + Commands: + - reward: 1 + Dst: + Object: fighter + Commands: + - decr: health + - eq: + Arguments: [health, 0] + Commands: + - remove: true + - set: [player_done, 1] + - incr: player_done_count + + - Name: move + Behaviours: + - Src: + Object: fighter + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + +Objects: + - Name: fighter + MapCharacter: f + Variables: + - Name: health + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + Block2D: + - Shape: triangle + Color: [0.6, 0.2, 0.2] + Scale: 0.5 + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [0.5, 0.5, 0.5] + Shape: square diff --git a/python/griddly/GymWrapper.py b/python/griddly/GymWrapper.py index 874ac3b39..e67a782dd 100644 --- a/python/griddly/GymWrapper.py +++ b/python/griddly/GymWrapper.py @@ -49,8 +49,6 @@ def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType self._players = [] self.player_count = self.gdy.get_player_count() - self.object_names = self.gdy.get_object_names() - self._global_observer_type = global_observer_type self._player_observer_type = [] @@ -183,6 +181,9 @@ def initialize_spaces(self): self.observation_space = observation_space + self.object_names = self.game.get_object_names() + self.variable_names = self.game.get_object_variable_names() + self._vector2rgb = Vector2RGB(10, len(self.object_names)) self.action_space = self._create_action_space() diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index 6ef6074dc..ee1284a28 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -13,12 +13,14 @@ from griddly.RenderTools import VideoRecorder + class RecordingState(Enum): NOT_RECORDING = 1 WAITING_FOR_EPISODE_START = 2 BEFORE_RECORDING = 3 RECORDING = 4 + class RLlibWrapper(GymWrapper): """ Wraps a Griddly environment for compatibility with RLLib. @@ -60,11 +62,12 @@ def __init__(self, env_config): super().reset() + self._recording_state = None + self._env_steps = 0 + if self._record_video_config is not None: self._recording_state = RecordingState.BEFORE_RECORDING - self._env_steps = 0 self._record_frequency = self._record_video_config.get('frequency', 1000) - self.set_transform() @@ -107,25 +110,26 @@ def _transform(self, observation): return transformed_obs def _after_step(self, observation, reward, done, info): - if self._recording_state is RecordingState.NOT_RECORDING and self._env_steps % self._record_frequency == 0: - self._recording_state = RecordingState.WAITING_FOR_EPISODE_START - - if self._recording_state == RecordingState.BEFORE_RECORDING: - global_obs = self.render(observer='global', mode='rgb_array') - self._global_recorder = VideoRecorder() - self._global_recorder.start(f'global_video_{uuid1()}_{self._env_steps}.mp4', global_obs.shape) - self._recording_state = RecordingState.RECORDING - - if self._recording_state == RecordingState.RECORDING: - global_obs = self.render(observer='global', mode='rgb_array') - self._global_recorder.add_frame(global_obs) - if done: - self._recording_state = RecordingState.NOT_RECORDING - self._global_recorder.close() - - if self._recording_state == RecordingState.WAITING_FOR_EPISODE_START: - if done: - self._recording_state = RecordingState.BEFORE_RECORDING + if self._recording_state is not None: + if self._recording_state is RecordingState.NOT_RECORDING and self._env_steps % self._record_frequency == 0: + self._recording_state = RecordingState.WAITING_FOR_EPISODE_START + + if self._recording_state == RecordingState.BEFORE_RECORDING: + global_obs = self.render(observer='global', mode='rgb_array') + self._global_recorder = VideoRecorder() + self._global_recorder.start(f'global_video_{uuid1()}_{self._env_steps}.mp4', global_obs.shape) + self._recording_state = RecordingState.RECORDING + + if self._recording_state == RecordingState.RECORDING: + global_obs = self.render(observer='global', mode='rgb_array') + self._global_recorder.add_frame(global_obs) + if done: + self._recording_state = RecordingState.NOT_RECORDING + self._global_recorder.close() + + if self._recording_state == RecordingState.WAITING_FOR_EPISODE_START: + if done: + self._recording_state = RecordingState.BEFORE_RECORDING def set_transform(self): """ @@ -145,11 +149,8 @@ def reset(self, **kwargs): def step(self, action): observation, reward, done, info = super().step(action) - - self._after_step(observation, reward, done, info) - if reward == 0.0: - reward = -0.1 + self._after_step(observation, reward, done, info) self._env_steps += 1 @@ -166,32 +167,57 @@ def __init__(self, env_config): self._player_done_variable = env_config.get('player_done_variable', None) + # Used to keep track of agents that are active in the environment + self._active_agents = set() + assert self.player_count > 1, 'RLlibMultiAgentWrapper can only be used with environments that have multiple agents' def _to_multi_agent_map(self, data): - return {p: obs for p, obs in enumerate(data)} + return {a: data[a-1] for a in self._active_agents} def reset(self, **kwargs): obs = super().reset(**kwargs) + self._active_agents.update([a+1 for a in range(self.player_count)]) return self._to_multi_agent_map(obs) + def _resolve_player_done_variable(self): + resolved_variables = self.game.get_global_variable([self._player_done_variable]) + return resolved_variables[self._player_done_variable] + def step(self, action_dict: MultiAgentDict): actions_array = np.zeros((self.player_count, *self.action_space.shape)) for agent_id, action in action_dict.items(): - actions_array[agent_id] = action + actions_array[agent_id-1] = action obs, reward, all_done, _ = super().step(actions_array) - done = {'__all__': all_done} + done_map = {'__all__': all_done} if self._player_done_variable is not None: - player_done = self.game.get_global_variable([self._player_done_variable]) + griddly_players_done = self._resolve_player_done_variable() + + for agent_id in self._active_agents: + done_map[agent_id] = griddly_players_done[agent_id] == 1 or all_done else: for p in range(self.player_count): - done[p + 1] = False + done_map[p] = False - info = {} if self._invalid_action_masking: - info = self._to_multi_agent_map(self._build_valid_action_trees()) + info_map = self._to_multi_agent_map(self._build_valid_action_trees()) + else: + info_map = self._to_multi_agent_map(defaultdict(dict)) + + obs_map = self._to_multi_agent_map(obs) + reward_map = self._to_multi_agent_map(reward) + + # Finally remove any agent ids that are done + for agent_id, is_done in done_map.items(): + if is_done: + self._active_agents.discard(agent_id) + + assert len(obs_map) == len(reward_map) + assert len(obs_map) == len(done_map)-1 + assert len(obs_map) == len(info_map) + - return self._to_multi_agent_map(obs), self._to_multi_agent_map(reward), done, info + return obs_map, reward_map, done_map, info_map diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index abf38afb7..83c8e80ce 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -3,13 +3,14 @@ #include #include "../../Grid.hpp" +#include "../../Util/util.hpp" #include "../Actions/Action.hpp" #include "ObjectGenerator.hpp" namespace griddly { -Object::Object(std::string objectName, uint32_t id, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator) - : objectName_(objectName), id_(id), zIdx_(zIdx), objectGenerator_(objectGenerator) { +Object::Object(std::string objectName, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator) + : objectName_(objectName), zIdx_(zIdx), objectGenerator_(objectGenerator) { availableVariables.insert({"_x", x_}); availableVariables.insert({"_y", y_}); @@ -40,10 +41,6 @@ glm::ivec2 Object::getLocation() const { return location; }; -uint32_t Object::getObjectId() const { - return id_; -} - std::string Object::getDescription() const { return fmt::format("{0}@[{1}, {2}]", objectName_, *x_, *y_); } @@ -53,30 +50,30 @@ BehaviourResult Object::onActionSrc(std::string destinationObjectName, std::shar auto behavioursForActionIt = srcBehaviours_.find(actionName); if (behavioursForActionIt == srcBehaviours_.end()) { - return {true, 0}; + return {true}; } auto &behavioursForAction = behavioursForActionIt->second; auto behavioursForActionAndDestinationObject = behavioursForAction.find(destinationObjectName); if (behavioursForActionAndDestinationObject == behavioursForAction.end()) { - return {true, 0}; + return {true}; } spdlog::debug("Executing behaviours for source [{0}] -> {1} -> {2}", getObjectName(), actionName, destinationObjectName); auto &behaviours = behavioursForActionAndDestinationObject->second; - int rewards = 0; + std::unordered_map rewardAccumulator; for (auto &behaviour : behaviours) { auto result = behaviour(action); - rewards += result.reward; + accumulateRewards(rewardAccumulator, result.rewards); if (result.abortAction) { - return {true, rewards}; + return {true, rewardAccumulator}; } } - return {false, rewards}; + return {false, rewardAccumulator}; } BehaviourResult Object::onActionDst(std::shared_ptr action) { @@ -86,30 +83,30 @@ BehaviourResult Object::onActionDst(std::shared_ptr action) { auto behavioursForActionIt = dstBehaviours_.find(actionName); if (behavioursForActionIt == dstBehaviours_.end()) { - return {true, 0}; + return {true}; } auto &behavioursForAction = behavioursForActionIt->second; auto behavioursForActionAndDestinationObject = behavioursForAction.find(sourceObjectName); if (behavioursForActionAndDestinationObject == behavioursForAction.end()) { - return {true, 0}; + return {true}; } spdlog::debug("Executing behaviours for destination {0} -> {1} -> [{2}]", sourceObjectName, actionName, getObjectName()); auto &behaviours = behavioursForActionAndDestinationObject->second; - int rewards = 0; + std::unordered_map rewardAccumulator; for (auto &behaviour : behaviours) { auto result = behaviour(action); - rewards += result.reward; + accumulateRewards(rewardAccumulator, result.rewards); if (result.abortAction) { - return {true, rewards}; + return {true, rewardAccumulator}; } } - return {false, rewards}; + return {false, rewardAccumulator}; } std::unordered_map> Object::resolveVariables(BehaviourCommandArguments commandArguments) { @@ -176,58 +173,52 @@ BehaviourFunction Object::instantiateConditionalBehaviour(std::string commandNam auto a = variablePointers["0"]; auto b = variablePointers["1"]; - return [this, condition, conditionalBehaviours, a, b](std::shared_ptr action) { + return [this, condition, conditionalBehaviours, a, b](std::shared_ptr action) -> BehaviourResult { if (condition(a->resolve(action), b->resolve(action))) { - int32_t rewards = 0; + std::unordered_map rewardAccumulator; for (auto &behaviour : conditionalBehaviours) { auto result = behaviour(action); - rewards += result.reward; + accumulateRewards(rewardAccumulator, result.rewards); if (result.abortAction) { - return BehaviourResult{true, rewards}; + return {true, rewardAccumulator}; } } - return BehaviourResult{false, rewards}; + return {false, rewardAccumulator}; } - return BehaviourResult{false, 0}; + return {}; }; } BehaviourFunction Object::instantiateBehaviour(std::string commandName, BehaviourCommandArguments commandArguments) { // Command just used in tests if (commandName == "nop") { - return [this](std::shared_ptr action) { - return BehaviourResult{false, 0}; + return [this](std::shared_ptr action) -> BehaviourResult { + return {}; }; } + // reward the player that owns this particular object, otherwise warn if (commandName == "reward") { auto value = commandArguments["0"].as(0); - return [this, value](std::shared_ptr action) { - return BehaviourResult{false, value}; - }; - } - - if (commandName == "override") { - auto abortAction = commandArguments["0"].as(false); - auto reward = commandArguments["1"].as(0); - return [this, abortAction, reward](std::shared_ptr action) { - return BehaviourResult{abortAction, reward}; + return [this, value](std::shared_ptr action) -> BehaviourResult { + // Find the player id of this object and give rewards to this player. + return {false, {{*playerId_, value}}}; }; } if (commandName == "change_to") { auto objectName = commandArguments["0"].as(); - return [this, objectName](std::shared_ptr action) { + return [this, objectName](std::shared_ptr action) -> BehaviourResult { spdlog::debug("Changing object={0} to {1}", getObjectName(), objectName); auto playerId = getPlayerId(); auto location = getLocation(); auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_->getGlobalVariables()); removeObject(); grid_->addObject(location, newObject); - return BehaviourResult(); + return {}; }; } @@ -235,9 +226,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; auto b = variablePointers["1"]; - return [this, a, b](std::shared_ptr action) { + return [this, a, b](std::shared_ptr action) -> BehaviourResult { *a->resolve_ptr(action) += b->resolve(action); - return BehaviourResult(); + return {}; }; } @@ -245,9 +236,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; auto b = variablePointers["1"]; - return [this, a, b](std::shared_ptr action) { + return [this, a, b](std::shared_ptr action) -> BehaviourResult { *a->resolve_ptr(action) -= b->resolve(action); - return BehaviourResult(); + return {}; }; } @@ -255,54 +246,54 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; auto b = variablePointers["1"]; - return [this, a, b](std::shared_ptr action) { + return [this, a, b](std::shared_ptr action) -> BehaviourResult { *a->resolve_ptr(action) = b->resolve(action); - return BehaviourResult(); + return {}; }; } if (commandName == "incr") { auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; - return [this, a](std::shared_ptr action) { + return [this, a](std::shared_ptr action) -> BehaviourResult { (*a->resolve_ptr(action)) += 1; - return BehaviourResult(); + return {}; }; } if (commandName == "decr") { auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; - return [this, a](std::shared_ptr action) { + return [this, a](std::shared_ptr action) -> BehaviourResult { (*a->resolve_ptr(action)) -= 1; - return BehaviourResult(); + return {}; }; } if (commandName == "rot") { if (commandArguments["0"].as() == "_dir") { - return [this](std::shared_ptr action) { + return [this](std::shared_ptr action) -> BehaviourResult { orientation_ = DiscreteOrientation(action->getOrientationVector()); // redraw the current location grid_->invalidateLocation(getLocation()); - return BehaviourResult(); + return {}; }; } } if (commandName == "mov") { if (commandArguments["0"].as() == "_dest") { - return [this](std::shared_ptr action) { + return [this](std::shared_ptr action) -> BehaviourResult { auto objectMoved = moveObject(action->getDestinationLocation()); - return BehaviourResult{!objectMoved}; + return {!objectMoved}; }; } if (commandArguments["0"].as() == "_src") { - return [this](std::shared_ptr action) { + return [this](std::shared_ptr action) -> BehaviourResult { auto objectMoved = moveObject(action->getSourceLocation()); - return BehaviourResult{!objectMoved}; + return {!objectMoved}; }; } @@ -310,23 +301,23 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou if (variablePointers.size() != 2) { spdlog::error("Bad mov command detected! There should be two arguments but {0} were provided. This command will be ignored.", variablePointers.size()); - return [this](std::shared_ptr action) { - return BehaviourResult{false, 0}; + return [this](std::shared_ptr action) -> BehaviourResult { + return {}; }; } auto x = variablePointers["0"]; auto y = variablePointers["1"]; - return [this, x, y](std::shared_ptr action) { + return [this, x, y](std::shared_ptr action) -> BehaviourResult { auto objectMoved = moveObject({x->resolve(action), y->resolve(action)}); - return BehaviourResult{!objectMoved}; + return {!objectMoved}; }; } if (commandName == "cascade") { auto a = commandArguments["0"].as(); - return [this, a](std::shared_ptr action) { + return [this, a](std::shared_ptr action) -> BehaviourResult { if (a == "_dest") { std::shared_ptr cascadedAction = std::shared_ptr(new Action(grid_, action->getActionName(), action->getDelay())); @@ -338,19 +329,14 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou spdlog::debug("Cascade vector [{0},{1}]", vectorToDest.x, vectorToDest.y); spdlog::debug("Cascading action to [{0},{1}], dst: [{2}, {3}]", sourceLocation.x, sourceLocation.y, destinationLocation.x, destinationLocation.y); - auto rewards = grid_->performActions(0, {cascadedAction}); + auto actionRewards = grid_->performActions(0, {cascadedAction}); - int32_t totalRewards = 0; - for (auto r : rewards) { - totalRewards += r; - } - - return BehaviourResult{false, totalRewards}; + return {false, actionRewards}; } spdlog::warn("The only supported variable for cascade is _dest."); - return BehaviourResult{true, 0}; + return {true}; }; } @@ -361,7 +347,7 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto actionId = commandArguments["ActionId"].as(0); // Resolve source object - return [this, actionName, delay, randomize, actionId](std::shared_ptr action) { + return [this, actionName, delay, randomize, actionId](std::shared_ptr action) -> BehaviourResult { std::shared_ptr newAction = std::shared_ptr(new Action(grid_, actionName, delay)); InputMapping fallbackInputMapping; @@ -377,44 +363,39 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou newAction->init(shared_from_this(), inputMapping.vectorToDest, inputMapping.orientationVector, inputMapping.relative); auto rewards = grid_->performActions(0, {newAction}); - int32_t totalRewards = 0; - for (auto r : rewards) { - totalRewards += r; - } - - return BehaviourResult{false, totalRewards}; + return {false, rewards}; }; } if (commandName == "remove") { - return [this](std::shared_ptr action) { + return [this](std::shared_ptr action) -> BehaviourResult { removeObject(); - return BehaviourResult(); + return {}; }; } if (commandName == "set_tile") { auto tileId = commandArguments["0"].as(); - return [this, tileId](std::shared_ptr action) { + return [this, tileId](std::shared_ptr action) -> BehaviourResult { setRenderTileId(tileId); - return BehaviourResult(); + return {}; }; } if (commandName == "spawn") { auto objectName = commandArguments["0"].as(); - return [this, objectName](std::shared_ptr action) { + return [this, objectName](std::shared_ptr action) -> BehaviourResult { auto destinationLocation = action->getDestinationLocation(); spdlog::debug("Spawning object={0} in location [{1},{2}]", objectName, destinationLocation.x, destinationLocation.y); auto playerId = getPlayerId(); auto newObject = objectGenerator_->newInstance(objectName, playerId, grid_->getGlobalVariables()); grid_->addObject(destinationLocation, newObject); - return BehaviourResult(); + return {}; }; } throw std::invalid_argument(fmt::format("Unknown or badly defined command {0}.", commandName)); -} // namespace griddly +} void Object::addPrecondition(std::string actionName, std::string destinationObjectName, std::string commandName, BehaviourCommandArguments commandArguments) { spdlog::debug("Adding action precondition command={0} when action={1} is performed on object={2} by object={3}", commandName, actionName, destinationObjectName, getObjectName()); diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index b1cd18024..94870d2ff 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -47,7 +47,7 @@ struct SingleInputMapping { struct BehaviourResult { bool abortAction = false; - int32_t reward = 0; + std::unordered_map rewards; }; class Object : public std::enable_shared_from_this { @@ -62,8 +62,6 @@ class Object : public std::enable_shared_from_this { virtual std::string getObjectRenderTileName() const; - virtual uint32_t getObjectId() const; - virtual std::string getDescription() const; virtual uint32_t getPlayerId() const; @@ -98,7 +96,7 @@ class Object : public std::enable_shared_from_this { virtual std::vector> getInitialActions(); virtual void setInitialActionDefinitions(std::vector actionDefinitions); - Object(std::string objectName, uint32_t id, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator); + Object(std::string objectName, uint32_t playerId, uint32_t zIdx, std::unordered_map> availableVariables, std::shared_ptr objectGenerator); ~Object(); @@ -111,7 +109,6 @@ class Object : public std::enable_shared_from_this { std::shared_ptr playerId_ = std::make_shared(0); const std::string objectName_; - const uint32_t id_; const uint32_t zIdx_; uint32_t renderTileId_ = 0; bool isPlayerAvatar_ = false; diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp index 8ab3ad9e6..7053f3f64 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp @@ -23,10 +23,7 @@ void ObjectGenerator::defineNewObject(std::string objectName, uint32_t zIdx, cha objectDefinition.variableDefinitions = variableDefinitions; objectDefinitions_.insert({objectName, std::make_shared(objectDefinition)}); - objectChars_[mapChar] = objectName; - objectIds_.insert({objectName, objectNames_.size()}); - objectNames_.push_back(objectName); } void ObjectGenerator::defineActionBehaviour( @@ -81,8 +78,7 @@ std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr t } auto objectZIdx = objectDefinition->zIdx; - auto id = objectIds_[objectName]; - auto initializedObject = std::shared_ptr(new Object(objectName, id, playerId, objectZIdx, availableVariables, shared_from_this())); + auto initializedObject = std::shared_ptr(new Object(objectName, playerId, objectZIdx, availableVariables, shared_from_this())); if (objectName == avatarObject_) { initializedObject->markAsPlayerAvatar(); @@ -163,8 +159,7 @@ std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uin } auto objectZIdx = objectDefinition->zIdx; - auto id = objectIds_[objectName]; - auto initializedObject = std::shared_ptr(new Object(objectName, id, playerId, objectZIdx, availableVariables, shared_from_this())); + auto initializedObject = std::shared_ptr(new Object(objectName, playerId, objectZIdx, availableVariables, shared_from_this())); if (isAvatar) { initializedObject->markAsPlayerAvatar(); @@ -231,10 +226,6 @@ std::string &ObjectGenerator::getObjectNameFromMapChar(char character) { return objectCharIt->second; } -const std::vector &ObjectGenerator::getObjectNames() const { - return objectNames_; -} - std::shared_ptr &ObjectGenerator::getObjectDefinition(std::string objectName) { auto objectDefinitionIt = objectDefinitions_.find(objectName); if (objectDefinitionIt == objectDefinitions_.end()) { diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp index 712df031a..2f6c8e13a 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp @@ -54,13 +54,9 @@ class ObjectGenerator : public std::enable_shared_from_this { virtual std::unordered_map> getObjectDefinitions() const; - virtual const std::vector& getObjectNames() const; - private: std::unordered_map objectChars_; std::unordered_map> objectDefinitions_; - std::unordered_map objectIds_; - std::vector objectNames_; std::string avatarObject_; std::unordered_map actionInputsDefinitions_; diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index fc347d95f..4f36aa643 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -15,7 +15,7 @@ class Player; struct ActionResult { std::unordered_map playerStates; bool terminated; - std::vector rewards; + int32_t reward; }; struct ObjectInfo { @@ -91,8 +91,13 @@ class GameProcess : public std::enable_shared_from_this { bool isInitialized_ = false; + // Should the game process reset itself or rely on external reset + bool autoReset_ = false; + private: uint8_t* resetObservers(); ObserverConfig getObserverConfig(ObserverType observerType) const; + + }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index caf4b66b0..28ada11d3 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -33,8 +33,8 @@ void Grid::resetMap(uint32_t width, uint32_t height) { occupiedLocations_.clear(); objects_.clear(); objectCounters_.clear(); - objectNames_.clear(); - objectVariableNames_.clear(); + objectIds_.clear(); + objectVariableIds_.clear(); delayedActions_ = {}; gameTicks_ = std::make_shared(0); @@ -117,7 +117,7 @@ const std::unordered_set& Grid::getUpdatedLocations(uint32_t playerI return updatedLocations_[playerId]; } -int32_t Grid::executeAndRecord(uint32_t playerId, std::shared_ptr action) { +std::unordered_map Grid::executeAndRecord(uint32_t playerId, std::shared_ptr action) { if (recordEvents_) { auto event = buildGridEvent(action, playerId, *gameTicks_); auto reward = executeAction(playerId, action); @@ -128,7 +128,7 @@ int32_t Grid::executeAndRecord(uint32_t playerId, std::shared_ptr action } } -int32_t Grid::executeAction(uint32_t playerId, std::shared_ptr action) { +std::unordered_map Grid::executeAction(uint32_t playerId, std::shared_ptr action) { auto sourceObject = action->getSourceObject(); auto destinationObject = action->getDestinationObject(); @@ -139,40 +139,40 @@ int32_t Grid::executeAction(uint32_t playerId, std::shared_ptr action) { if (objects_.find(sourceObject) == objects_.end() && action->getDelay() > 0) { spdlog::debug("Delayed action for object that no longer exists."); - return 0; + return {}; } if (sourceObject == nullptr) { spdlog::debug("Cannot perform action on empty space."); - return 0; + return {}; } auto sourceObjectPlayerId = sourceObject->getPlayerId(); if (playerId != 0 && sourceObjectPlayerId != playerId) { spdlog::debug("Cannot perform action on object not owned by player. Object owner {0}, Player owner {1}", sourceObjectPlayerId, playerId); - return 0; + return {}; } if (sourceObject->isValidAction(action)) { - int reward = 0; + std::unordered_map rewardAccumulator; if (destinationObject != nullptr && destinationObject.get() != sourceObject.get()) { auto dstBehaviourResult = destinationObject->onActionDst(action); - reward += dstBehaviourResult.reward; + accumulateRewards(rewardAccumulator, dstBehaviourResult.rewards); if (dstBehaviourResult.abortAction) { spdlog::debug("Action {0} aborted by destination object behaviour.", action->getDescription()); - return reward; + return rewardAccumulator; } } auto srcBehaviourResult = sourceObject->onActionSrc(originalDestinationObjectName, action); - reward += srcBehaviourResult.reward; - return reward; + accumulateRewards(rewardAccumulator, srcBehaviourResult.rewards); + return rewardAccumulator; } else { spdlog::debug("Cannot perform action={0} on object={1}", action->getActionName(), sourceObject->getObjectName()); - return 0; + return {}; } } @@ -207,13 +207,13 @@ GridEvent Grid::buildGridEvent(std::shared_ptr action, uint32_t playerId return event; } -void Grid::recordGridEvent(GridEvent event, int32_t reward) { - event.reward = reward; +void Grid::recordGridEvent(GridEvent event, std::unordered_map rewards) { + event.rewards = rewards; eventHistory_.push_back(event); } -std::vector Grid::performActions(uint32_t playerId, std::vector> actions) { - std::vector rewards; +std::unordered_map Grid::performActions(uint32_t playerId, std::vector> actions) { + std::unordered_map rewardAccumulator; spdlog::trace("Tick {0}", *gameTicks_); @@ -222,11 +222,12 @@ std::vector Grid::performActions(uint32_t playerId, std::vectorgetDelay() > 0) { delayAction(playerId, action); } else { - rewards.push_back(executeAndRecord(playerId, action)); + auto actionRewards = executeAndRecord(playerId, action); + accumulateRewards(rewardAccumulator, actionRewards); } } - return rewards; + return rewardAccumulator; } void Grid::delayAction(uint32_t playerId, std::shared_ptr action) { @@ -256,7 +257,8 @@ std::unordered_map Grid::update() { spdlog::debug("Popped delayed action {0} at game tick {1}", action->getDescription(), *gameTicks_); - delayedRewards[playerId] += executeAndRecord(playerId, action); + auto delayedActionRewards = executeAndRecord(playerId, action); + accumulateRewards(delayedRewards, delayedActionRewards); } return delayedRewards; @@ -301,22 +303,48 @@ std::shared_ptr Grid::getObject(glm::ivec2 location) const { return nullptr; } -uint32_t Grid::getUniqueObjectCount() const { - return objectNames_.size(); +const std::unordered_map& Grid::getObjectIds() const { + return objectIds_; +} + + +const std::unordered_map& Grid::getObjectVariableIds() const { + return objectVariableIds_; } -const std::set& Grid::getObjectNames() const { - return objectNames_; +const std::vector Grid::getObjectNames() const { + auto namesCount = objectIds_.size(); + std::vector orderedNames(namesCount); + + for(auto& objectIdIt : objectIds_) { + auto name = objectIdIt.first; + auto idx = objectIdIt.second; + orderedNames[idx] = name; + } + + return orderedNames; } -const std::set& Grid::getObjectVariableNames() const { - return objectVariableNames_; +const std::vector Grid::getObjectVariableNames() const { + auto namesCount = objectVariableIds_.size(); + std::vector orderedNames(namesCount); + + for(auto& objectVariableIdIt : objectVariableIds_) { + auto name = objectVariableIdIt.first; + auto idx = objectVariableIdIt.second; + orderedNames[idx] = name; + } + + return orderedNames; } void Grid::initObject(std::string objectName, std::vector variableNames) { - objectNames_.insert(objectName); - objectVariableNames_.insert(variableNames.begin(), variableNames.end()); + objectIds_.insert({objectName, objectIds_.size()}); + + for(auto& variableName : variableNames) { + objectVariableIds_.insert({variableName, objectVariableIds_.size()}); + } } std::unordered_map> Grid::getObjectCounter(std::string objectName) { @@ -414,7 +442,7 @@ void Grid::enableHistory(bool enable) { recordEvents_ = enable; } -std::vector Grid::getHistory() const { +const std::vector& Grid::getHistory() const { return eventHistory_; } diff --git a/src/Griddly/Core/Grid.hpp b/src/Griddly/Core/Grid.hpp index 545b4bffd..3fb4fb31e 100644 --- a/src/Griddly/Core/Grid.hpp +++ b/src/Griddly/Core/Grid.hpp @@ -26,7 +26,7 @@ struct GridEvent { uint32_t playerId; std::string actionName; uint32_t tick = 0; - int32_t reward = 0; + std::unordered_map rewards; uint32_t delay = 0; std::string sourceObjectName; @@ -56,8 +56,8 @@ class Grid : public std::enable_shared_from_this { virtual void resetGlobalVariables(std::unordered_map globalVariableDefinitions); virtual void setGlobalVariables(std::unordered_map> globalVariableDefinitions); - virtual std::vector performActions(uint32_t playerId, std::vector> actions); - virtual int32_t executeAction(uint32_t playerId, std::shared_ptr action); + virtual std::unordered_map performActions(uint32_t playerId, std::vector> actions); + virtual std::unordered_map executeAction(uint32_t playerId, std::shared_ptr action); virtual void delayAction(uint32_t playerId, std::shared_ptr action); virtual std::unordered_map update(); @@ -94,19 +94,24 @@ class Grid : public std::enable_shared_from_this { virtual std::shared_ptr getObject(glm::ivec2 location) const; /** - * Gets the number of unique objects in the grid + * Get a list of the objects and their Ids in this grid */ - virtual uint32_t getUniqueObjectCount() const; + virtual const std::unordered_map& getObjectIds() const; + + /** + * Get a list of the object variables and their Ids in this grid + */ + virtual const std::unordered_map& getObjectVariableIds() const; /** * Gets an ordered list of objectVariableNames */ - virtual const std::set& getObjectVariableNames() const; + virtual const std::vector getObjectVariableNames() const; /** * Gets an ordered list of objectNames */ - virtual const std::set& getObjectNames() const; + virtual const std::vector getObjectNames() const; /** * Get a mapping of the avatar objects for players in the environment @@ -118,14 +123,14 @@ class Grid : public std::enable_shared_from_this { virtual const std::unordered_map>>& getGlobalVariables() const; virtual void enableHistory(bool enable); - virtual std::vector getHistory() const; + virtual const std::vector& getHistory() const; virtual void purgeHistory(); private: GridEvent buildGridEvent(std::shared_ptr action, uint32_t playerId, uint32_t tick); - void recordGridEvent(GridEvent event, int32_t reward); + void recordGridEvent(GridEvent event, std::unordered_map rewards); - int32_t executeAndRecord(uint32_t playerId, std::shared_ptr action); + std::unordered_map executeAndRecord(uint32_t playerId, std::shared_ptr action); uint32_t height_; uint32_t width_; @@ -136,8 +141,8 @@ class Grid : public std::enable_shared_from_this { // This is so we can highly optimize observers to only re-render changed grid locations std::vector> updatedLocations_; - std::set objectNames_; - std::set objectVariableNames_; + std::unordered_map objectIds_; + std::unordered_map objectVariableIds_; std::unordered_set> objects_; std::unordered_map occupiedLocations_; std::unordered_map>> objectCounters_; diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index 8d1aca11f..1de98a81b 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -29,11 +29,12 @@ void VectorObserver::resetShape() { gridBoundary_.x = grid_->getWidth(); gridBoundary_.y = grid_->getHeight(); - observationChannels_ = grid_->getUniqueObjectCount(); + observationChannels_ = grid_->getObjectIds().size(); // Always in order objects, player, orientation, variables. if (observerConfig_.includePlayerId) { + channelsBeforePlayerCount_ = observationChannels_; observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" } @@ -44,7 +45,7 @@ void VectorObserver::resetShape() { if (observerConfig_.includeVariables) { channelsBeforeVariables_ = observationChannels_; - observationChannels_ += grid_->getObjectVariableNames().size(); + observationChannels_ += grid_->getObjectVariableIds().size(); } observationShape_ = {observationChannels_, gridWidth_, gridHeight_}; @@ -61,8 +62,6 @@ uint8_t* VectorObserver::reset() { }; void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 outputLocation, bool resetLocation) const { - auto uniqueObjectCount = grid_->getUniqueObjectCount(); - auto memPtr = observation_.get() + observationChannels_ * (gridWidth_ * outputLocation.y + outputLocation.x); if (resetLocation) { @@ -74,7 +73,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output bool processTopLayer = true; for (auto& objectIt : grid_->getObjectsAt(objectLocation)) { auto object = objectIt.second; - auto memPtrObject = memPtr + object->getObjectId(); + auto memPtrObject = memPtr + grid_->getObjectIds().at(object->getObjectName()); *memPtrObject = 1; if (processTopLayer) { @@ -95,7 +94,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output playerIdx = objectPlayerId; } - auto playerMemPtr = memPtr + uniqueObjectCount + playerIdx; + auto playerMemPtr = memPtr + channelsBeforePlayerCount_ + playerIdx; *playerMemPtr = 1; } @@ -122,9 +121,9 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output auto variableName = variableIt.first; // If the variable is one of the variables defined in the object, get the index of the variable and set it to the variable's value - auto objectVariableIt = grid_->getObjectVariableNames().find(variableName); - if (objectVariableIt != grid_->getObjectVariableNames().end()) { - uint32_t variableIdx = std::distance(grid_->getObjectVariableNames().begin(), grid_->getObjectVariableNames().begin()); + auto objectVariableIt = grid_->getObjectVariableIds().find(variableName); + if (objectVariableIt != grid_->getObjectVariableIds().end()) { + uint32_t variableIdx = objectVariableIt->second; auto variableMemPtr = memPtr + channelsBeforeVariables_ + variableIdx; *variableMemPtr = variableValue; diff --git a/src/Griddly/Core/Observers/VectorObserver.hpp b/src/Griddly/Core/Observers/VectorObserver.hpp index 0a65ded62..4d21dcd0c 100644 --- a/src/Griddly/Core/Observers/VectorObserver.hpp +++ b/src/Griddly/Core/Observers/VectorObserver.hpp @@ -26,6 +26,7 @@ class VectorObserver : public Observer { std::shared_ptr observation_; bool trackAvatar_; uint32_t observationChannels_; + uint32_t channelsBeforePlayerCount_; uint32_t channelsBeforeRotation_; uint32_t channelsBeforeVariables_; }; diff --git a/src/Griddly/Core/Players/Player.cpp b/src/Griddly/Core/Players/Player.cpp index 013fafbc0..ead9e22cc 100644 --- a/src/Griddly/Core/Players/Player.cpp +++ b/src/Griddly/Core/Players/Player.cpp @@ -68,9 +68,7 @@ ActionResult Player::performActions(std::vector> actions auto actionResult = gameProcess_->performActions(id_, actions, updateTicks); // Update the player's score - for (auto r : actionResult.rewards) { - *score_ += r; - } + *score_ += actionResult.reward; return actionResult; } diff --git a/src/Griddly/Core/TurnBasedGameProcess.cpp b/src/Griddly/Core/TurnBasedGameProcess.cpp index a19ff9bad..93b1dbe8f 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.cpp +++ b/src/Griddly/Core/TurnBasedGameProcess.cpp @@ -3,6 +3,7 @@ #include #include "DelayedActionQueueItem.hpp" +#include "Util/util.hpp" namespace griddly { @@ -20,36 +21,38 @@ TurnBasedGameProcess::~TurnBasedGameProcess() { ActionResult TurnBasedGameProcess::performActions(uint32_t playerId, std::vector> actions, bool updateTicks) { spdlog::debug("Performing turn based actions for player {0}", playerId); - auto rewards = grid_->performActions(playerId, actions); + auto stepRewards = grid_->performActions(playerId, actions); + + int32_t reward = 0; if (updateTicks) { spdlog::debug("Updating Grid"); auto delayedRewards = grid_->update(); - for (auto delayedReward : delayedRewards) { - auto playerId = delayedReward.first; - auto reward = delayedReward.second; - delayedRewards_[playerId] += reward; - } + // rewards could come from delayed actions that are run at a particular time step + accumulateRewards(accumulatedRewards_, delayedRewards); + + // rewards resulting from player actions + accumulateRewards(accumulatedRewards_, stepRewards); - if (delayedRewards_[playerId] > 0) { - rewards.push_back(delayedRewards_[playerId]); + if (accumulatedRewards_[playerId] > 0) { + reward = accumulatedRewards_[playerId]; + // reset reward for this player as they are being returned here + accumulatedRewards_[playerId] = 0; } - // reset reward for this player as they are being returned here - delayedRewards_[playerId] = 0; auto terminationResult = terminationHandler_->isTerminated(); auto episodeComplete = terminationResult.terminated; - if (episodeComplete) { + if (episodeComplete && autoReset_) { reset(); } - return {terminationResult.playerStates, episodeComplete, rewards}; + return {terminationResult.playerStates, episodeComplete, reward}; } - return {{}, false, rewards}; + return {{}, false, reward}; } // This is only used in tests diff --git a/src/Griddly/Core/TurnBasedGameProcess.hpp b/src/Griddly/Core/TurnBasedGameProcess.hpp index f05585352..1ee6fd0e5 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.hpp +++ b/src/Griddly/Core/TurnBasedGameProcess.hpp @@ -25,6 +25,6 @@ class TurnBasedGameProcess : public GameProcess { private: static const std::string name_; - std::unordered_map delayedRewards_; + std::unordered_map accumulatedRewards_; }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/Util/util.hpp b/src/Griddly/Core/Util/util.hpp index ae3d1426b..f68e4620a 100644 --- a/src/Griddly/Core/Util/util.hpp +++ b/src/Griddly/Core/Util/util.hpp @@ -30,3 +30,9 @@ struct VectorPriorityQueue :std::priority_queue { typename C::iterator begin() { return std::priority_queue::c.begin(); } typename C::iterator end() { return std::priority_queue::c.end(); } }; + +inline void accumulateRewards(std::unordered_map& acc, std::unordered_map& values) { + for(auto valueIt : values) { + acc[valueIt.first] += valueIt.second; + } +} \ No newline at end of file diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index b392f0915..1af6ba890 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -95,7 +95,7 @@ std::shared_ptr setupAction(std::string actionName, std::shared_ptr< TEST(ObjectTest, getLocation) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 0, 1, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 0, 0, {}, nullptr)); object->init({5, 5}, mockGridPtr); @@ -106,22 +106,16 @@ TEST(ObjectTest, getLocation) { TEST(ObjectTest, getObjectName) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 0, 0, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 0, 0, {}, nullptr)); ASSERT_EQ(object->getObjectName(), "object"); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); } -TEST(ObjectTest, getObjectId) { - auto object = std::shared_ptr(new Object("object", 0, 0, 0, {}, nullptr)); - - ASSERT_EQ(object->getObjectId(), 0); -} - TEST(ObjectTest, getDescription) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 0, 1, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 0, 0, {}, nullptr)); object->init({9, 6}, mockGridPtr); @@ -132,7 +126,7 @@ TEST(ObjectTest, getDescription) { TEST(ObjectTest, getPlayerId) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 0, 2, 0, {}, nullptr)); + auto object = std::shared_ptr(new Object("object", 2, 0, {}, nullptr)); object->init({5, 5}, mockGridPtr); @@ -143,7 +137,7 @@ TEST(ObjectTest, getPlayerId) { TEST(ObjectTest, getVariables) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto object = std::shared_ptr(new Object("object", 0, 2, 0, {{"test_param", _V(20)}}, nullptr)); + auto object = std::shared_ptr(new Object("object", 2, 0, {{"test_param", _V(20)}}, nullptr)); ASSERT_EQ(*object->getVariableValue("test_param"), 20); @@ -157,43 +151,11 @@ TEST(ObjectTest, getVariables) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); } -// TEST(ObjectTest, initializeWithGlobalVariables) { -// auto mockGridPtr = std::shared_ptr(new MockGrid()); -// auto object0 = std::shared_ptr(new Object("object", 0, 0, 0, {{"local_param", _V(3)}}, nullptr)); -// auto object1 = std::shared_ptr(new Object("object", 0, 1, 0, {{"local_param", _V(4)}}, nullptr)); -// auto object2 = std::shared_ptr(new Object("object", 0, 2, 0, {{"local_param", _V(6)}}, nullptr)); - -// std::unordered_map>> globalVariables{ -// {"global_variable", {{0, _V(0)}}}, -// {"player_variable", {{1, _V(10)}, {2, _V(20)}}}}; - -// EXPECT_CALL(*mockGridPtr, getGlobalVariables) -// .WillRepeatedly(Return(globalVariables)); - -// ASSERT_EQ(*object0->getVariableValue("local_param"), 3); -// ASSERT_EQ(*object1->getVariableValue("local_param"), 4); -// ASSERT_EQ(*object2->getVariableValue("local_param"), 6); - -// object0->init({5, 6}, mockGridPtr); -// object1->init({5, 5}, mockGridPtr); -// object2->init({5, 7}, mockGridPtr); - -// ASSERT_EQ(*object1->getVariableValue("player_variable"), 10); -// ASSERT_EQ(*object2->getVariableValue("player_variable"), 20); - -// ASSERT_EQ(*object1->getVariableValue("global_variable"), 0); -// ASSERT_EQ(*object2->getVariableValue("global_variable"), 0); - -// ASSERT_EQ(object0->getVariableValue("player_variable"), nullptr); - -// EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); -// } - TEST(ObjectTest, actionBoundToSrc) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -209,8 +171,8 @@ TEST(ObjectTest, actionBoundToSrc) { TEST(ObjectTest, actionBoundToDst) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -228,7 +190,7 @@ TEST(ObjectTest, actionBoundToDst) { TEST(ObjectTest, actionDestinationObjectDifferentFromOriginalObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, glm::ivec2{1, 1}); @@ -245,8 +207,8 @@ TEST(ObjectTest, actionDestinationObjectDifferentFromOriginalObject) { TEST(ObjectTest, srcActionNoBehaviourForDstObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -263,8 +225,8 @@ TEST(ObjectTest, srcActionNoBehaviourForDstObject) { TEST(ObjectTest, srcActionNoBehaviourForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -278,8 +240,8 @@ TEST(ObjectTest, srcActionNoBehaviourForAction) { TEST(ObjectTest, dstActionNoBehaviourForDstObject) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -296,8 +258,8 @@ TEST(ObjectTest, dstActionNoBehaviourForDstObject) { TEST(ObjectTest, dstActionNoBehaviourForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction("action", srcObject, dstObject); @@ -315,7 +277,7 @@ struct CommandTestResult { }; std::shared_ptr setupObject(uint32_t playerId, std::string objectname, glm::ivec2 location, DiscreteOrientation orientation, std::unordered_map> initialVariables, std::shared_ptr mockGridPtr, std::shared_ptr objectGenerator) { - auto object = std::shared_ptr(new Object(objectname, 0, playerId, 0, initialVariables, objectGenerator)); + auto object = std::shared_ptr(new Object(objectname, playerId, 0, initialVariables, objectGenerator)); if (mockGridPtr != nullptr) { object->init(location, orientation, mockGridPtr); @@ -335,8 +297,12 @@ std::shared_ptr setupObject(uint32_t playerId, std::string objectname, g return setupObject(playerId, objectname, location, orientation, initialVariables, mockGridPtr, nullptr); } +std::shared_ptr setupObject(uint32_t playerId, std::string objectname, std::unordered_map> initialVariables) { + return setupObject(playerId, objectname, {0, 0}, initialVariables, nullptr); +} + std::shared_ptr setupObject(std::string objectname, std::unordered_map> initialVariables) { - return setupObject(0, objectname, {0, 0}, initialVariables, nullptr); + return setupObject(1, objectname, {0, 0}, initialVariables, nullptr); } BehaviourResult addCommandsAndExecute(ActionBehaviourType type, std::shared_ptr action, std::string commandName, BehaviourCommandArguments commandArgumentMap, std::unordered_map conditionalCommands, std::shared_ptr srcObjectPtr, std::shared_ptr dstObjectPtr) { @@ -352,7 +318,7 @@ BehaviourResult addCommandsAndExecute(ActionBehaviourType type, std::shared_ptr< } } - return {true, 0}; + return {true}; } BehaviourResult addCommandsAndExecute(ActionBehaviourType type, std::shared_ptr action, std::string commandName, BehaviourCommandArguments commandArgumentMap, std::shared_ptr srcObjectPtr, std::shared_ptr dstObjectPtr) { @@ -367,9 +333,9 @@ std::shared_ptr mockGrid() { return mockGridPtr; } -void verifyCommandResult(BehaviourResult result, bool abort, int32_t reward) { +void verifyCommandResult(BehaviourResult result, bool abort, std::unordered_map rewards) { ASSERT_EQ(result.abortAction, abort); - ASSERT_EQ(result.reward, reward); + ASSERT_EQ(result.rewards, rewards); } void verifyMocks(std::shared_ptr mockActionPtr, std::shared_ptr mockGridPtr = nullptr, std::shared_ptr mockObjectGenerator = nullptr) { @@ -415,42 +381,19 @@ MATCHER_P3(SingletonActionVectorMatcher, actionName, sourceObjectPtr, vectorToDe } TEST(ObjectTest, command_reward) { - auto srcObjectPtr = setupObject("srcObject", {}); - auto dstObjectPtr = setupObject("dstObject", {}); + auto srcObjectPtr = setupObject(1, "srcObject", {}); + auto dstObjectPtr = setupObject(3, "dstObject", {}); auto mockActionPtr = setupAction("action", srcObjectPtr, dstObjectPtr); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "reward", {{"0", _Y("10")}}, srcObjectPtr, dstObjectPtr); - auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "reward", {{"0", _Y("10")}}, srcObjectPtr, dstObjectPtr); + auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "reward", {{"0", _Y("-10")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 10); - verifyCommandResult(dstResult, false, 10); + verifyCommandResult(srcResult, false, {{1, 10}}); + verifyCommandResult(dstResult, false, {{3, -10}}); verifyMocks(mockActionPtr); } -TEST(ObjectTest, command_override) { - auto srcObjectPtr = setupObject("srcObject", {}); - auto dstObjectPtr = setupObject("dstObject", {}); - auto mockActionPtr1 = setupAction("override_true", srcObjectPtr, dstObjectPtr); - - auto srcResult1 = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr1, "override", {{"0", _Y("true")}, {"1", _Y("123")}}, srcObjectPtr, dstObjectPtr); - auto dstResult1 = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr1, "override", {{"0", _Y("true")}, {"1", _Y("123")}}, srcObjectPtr, dstObjectPtr); - - verifyCommandResult(srcResult1, true, 123); - verifyCommandResult(dstResult1, true, 123); - - auto mockActionPtr2 = setupAction("override_false", srcObjectPtr, dstObjectPtr); - - auto srcResult2 = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr2, "override", {{"0", _Y("false")}, {"1", _Y("-123")}}, srcObjectPtr, dstObjectPtr); - auto dstResult2 = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr2, "override", {{"0", _Y("false")}, {"1", _Y("-123")}}, srcObjectPtr, dstObjectPtr); - - verifyCommandResult(srcResult2, false, -123); - verifyCommandResult(dstResult2, false, -123); - - verifyMocks(mockActionPtr1); - verifyMocks(mockActionPtr2); -} - TEST(ObjectTest, command_set) { auto srcObjectPtr = setupObject("srcObject", {{"test_param", _V(20)}}); auto dstObjectPtr = setupObject("dstObject", {{"test_param", _V(20)}}); @@ -459,8 +402,8 @@ TEST(ObjectTest, command_set) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "set", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "set", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("test_param"), 5); ASSERT_EQ(*dstObjectPtr->getVariableValue("test_param"), 5); @@ -476,8 +419,8 @@ TEST(ObjectTest, command_add) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "add", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "add", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("test_param"), 25); ASSERT_EQ(*dstObjectPtr->getVariableValue("test_param"), 25); @@ -493,8 +436,8 @@ TEST(ObjectTest, command_sub) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "sub", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "sub", {{"0", _Y("test_param")}, {"1", _Y("5")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("test_param"), 15); ASSERT_EQ(*dstObjectPtr->getVariableValue("test_param"), 15); @@ -510,8 +453,8 @@ TEST(ObjectTest, command_incr) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "incr", {{"0", _Y("test_param")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "incr", {{"0", _Y("test_param")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("test_param"), 21); ASSERT_EQ(*dstObjectPtr->getVariableValue("test_param"), 21); @@ -527,8 +470,8 @@ TEST(ObjectTest, command_decr) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "decr", {{"0", _Y("test_param")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "decr", {{"0", _Y("test_param")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("test_param"), 19); ASSERT_EQ(*dstObjectPtr->getVariableValue("test_param"), 19); @@ -559,8 +502,8 @@ TEST(ObjectTest, command_mov_dest) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "mov", {{"0", _Y("_dest")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "mov", {{"0", _Y("_src")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(srcObjectPtr->getLocation(), actionDestination); ASSERT_EQ(*srcObjectPtr->getVariableValue("_x"), 4); @@ -593,8 +536,8 @@ TEST(ObjectTest, command_mov_action_src) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "mov", {{"0", _Y("_src")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "mov", {{"0", _Y("_src")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(srcObjectPtr->getLocation(), glm::ivec2(3, 3)); ASSERT_EQ(*srcObjectPtr->getVariableValue("_x"), 3); @@ -617,8 +560,8 @@ TEST(ObjectTest, command_mov_action_params) { auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "mov", {{"0", _Y("mov_x")}, {"1", _Y("mov_y")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(srcObjectPtr->getLocation(), glm::ivec2(7, 12)); ASSERT_EQ(dstObjectPtr->getLocation(), glm::ivec2(8, 10)); @@ -644,24 +587,24 @@ TEST(ObjectTest, command_cascade) { auto mockGridPtr = mockGrid(); auto srcObjectPtr = setupObject(1, "srcObject", glm::ivec2(0, 0), {}, mockGridPtr); - auto dstObjectPtr = setupObject(1, "dstObject", glm::ivec2(1, 0), {}, mockGridPtr); + auto dstObjectPtr = setupObject(3, "dstObject", glm::ivec2(1, 0), {}, mockGridPtr); auto mockActionPtr1 = setupAction("action1", srcObjectPtr, dstObjectPtr); auto mockActionPtr2 = setupAction("action2", srcObjectPtr, dstObjectPtr); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), ActionListMatcher("action1", 1))) .Times(1) - .WillOnce(Return(std::vector{1})); + .WillOnce(Return(std::unordered_map{{1, 1}})); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), ActionListMatcher("action2", 1))) .Times(1) - .WillOnce(Return(std::vector{2})); + .WillOnce(Return(std::unordered_map{{3, 2}})); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr1, "cascade", {{"0", _Y("_dest")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr2, "cascade", {{"0", _Y("_dest")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 1); - verifyCommandResult(dstResult, false, 2); + verifyCommandResult(srcResult, false, {{1, 1}}); + verifyCommandResult(dstResult, false, {{3, 2}}); verifyMocks(mockActionPtr1, mockGridPtr); verifyMocks(mockActionPtr2); @@ -683,8 +626,8 @@ TEST(ObjectTest, command_mapped_to_grid) { auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); auto mockGridPtr = mockGrid(); - auto srcObjectPtr = setupObject(1, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); - auto dstObjectPtr = setupObject(1, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto srcObjectPtr = setupObject(2, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(5, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); auto mockActionPtr = setupAction("do_exec", srcObjectPtr, dstObjectPtr); std::unordered_map mockInputDefinitions{ @@ -707,17 +650,17 @@ TEST(ObjectTest, command_mapped_to_grid) { EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonMappedToGridMatcher("mapped_to_grid", srcObjectPtr, gridDimensions))) .Times(1) - .WillRepeatedly(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{2, 3}})); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonMappedToGridMatcher("mapped_to_grid", dstObjectPtr, gridDimensions))) .Times(1) - .WillRepeatedly(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{5, 3}})); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("mapped_to_grid")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("mapped_to_grid")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 3); - verifyCommandResult(dstResult, false, 3); + verifyCommandResult(srcResult, false, {{2, 3}}); + verifyCommandResult(dstResult, false, {{5, 3}}); verifyMocks(mockActionPtr, mockGridPtr); } @@ -742,8 +685,8 @@ TEST(ObjectTest, command_exec_delayed) { auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); auto mockGridPtr = mockGrid(); - auto srcObjectPtr = setupObject(1, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); - auto dstObjectPtr = setupObject(1, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto srcObjectPtr = setupObject(10, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(2, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); auto mockActionPtr = setupAction("do_exec", srcObjectPtr, dstObjectPtr); std::unordered_map mockInputDefinitions{ @@ -762,17 +705,17 @@ TEST(ObjectTest, command_exec_delayed) { EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonDelayedActionVectorMatcher("exec_action", 10, srcObjectPtr, glm::ivec2(0, -1)))) .Times(1) - .WillRepeatedly(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{10, 3}})); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonDelayedActionVectorMatcher("exec_action", 10, dstObjectPtr, glm::ivec2(0, -1)))) .Times(1) - .WillRepeatedly(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{2, 6}})); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Delay", _Y("10")}, {"ActionId", _Y(2)}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Delay", _Y("10")}, {"ActionId", _Y(2)}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 3); - verifyCommandResult(dstResult, false, 3); + verifyCommandResult(srcResult, false, {{10, 3}}); + verifyCommandResult(dstResult, false, {{2, 6}}); verifyMocks(mockActionPtr, mockGridPtr); } @@ -795,8 +738,8 @@ TEST(ObjectTest, command_exec) { auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); auto mockGridPtr = mockGrid(); - auto srcObjectPtr = setupObject(1, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); - auto dstObjectPtr = setupObject(1, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto srcObjectPtr = setupObject(2, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(10, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); std::unordered_map mockInputDefinitions{ {"exec_action", {{ @@ -816,17 +759,17 @@ TEST(ObjectTest, command_exec) { EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", srcObjectPtr, glm::ivec2{0, -1}))) .Times(1) - .WillOnce(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{2, 3}})); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", dstObjectPtr, glm::ivec2{0, -1}))) .Times(1) - .WillOnce(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{10, 6}})); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 3); - verifyCommandResult(dstResult, false, 3); + verifyCommandResult(srcResult, false, {{2, 3}}); + verifyCommandResult(dstResult, false, {{10, 6}}); verifyMocks(mockActionPtr, mockGridPtr); } @@ -866,17 +809,17 @@ TEST(ObjectTest, command_exec_randomize) { EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", srcObjectPtr, glm::ivec2(-1, 0)))) .Times(1) - .WillOnce(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{1, 3}})); EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", dstObjectPtr, glm::ivec2(-1, 0)))) .Times(1) - .WillOnce(Return(std::vector{3})); + .WillOnce(Return(std::unordered_map{{1, 3}})); auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Randomize", _Y(true)}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"Randomize", _Y(true)}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 3); - verifyCommandResult(dstResult, false, 3); + verifyCommandResult(srcResult, false, {{1, 3}}); + verifyCommandResult(dstResult, false, {{1, 3}}); verifyMocks(mockActionPtr, mockGridPtr); } @@ -908,8 +851,8 @@ TEST(ObjectTest, command_remove) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "remove", {}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "remove", {}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); verifyMocks(mockActionPtr, mockGridPtr); } @@ -960,8 +903,8 @@ TEST(ObjectTest, command_change_to) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "change_to", {{"0", _Y("newObject")}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "change_to", {{"0", _Y("newObject")}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); verifyMocks(mockActionPtr, mockGridPtr, mockObjectGenerator); } @@ -994,8 +937,8 @@ TEST(ObjectTest, command_set_tile) { ASSERT_EQ(srcObjectPtr->getObjectRenderTileName(), "srcObject1"); ASSERT_EQ(dstObjectPtr->getObjectRenderTileName(), "dstObject1"); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); verifyMocks(mockActionPtr, mockGridPtr, mockObjectGenerator); } @@ -1028,7 +971,7 @@ TEST(ObjectTest, command_spawn) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "spawn", {{"0", _Y("newObject")}}, srcObjectPtr, nullptr); - verifyCommandResult(srcResult, false, 0); + verifyCommandResult(srcResult, false, {}); verifyMocks(mockActionPtr, mockGridPtr, mockObjectGenerator); } @@ -1057,8 +1000,8 @@ TEST(ObjectTest, command_eq) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "eq", {{"0", _Y("resource")}, {"1", _Y("0")}}, {{"incr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "eq", {{"0", _Y("resource")}, {"1", _Y("1")}}, {{"decr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("resource"), 1); ASSERT_EQ(*dstObjectPtr->getVariableValue("resource"), 0); @@ -1090,8 +1033,8 @@ TEST(ObjectTest, command_eq_qualifiers) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "eq", {{"0", _Y("dst.resource")}, {"1", _Y("1")}}, {{"incr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "eq", {{"0", _Y("src.resource")}, {"1", _Y("1")}}, {{"decr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("resource"), 1); ASSERT_EQ(*dstObjectPtr->getVariableValue("resource"), 0); @@ -1123,8 +1066,8 @@ TEST(ObjectTest, command_lt) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "lt", {{"0", _Y("resource")}, {"1", _Y("1")}}, {{"incr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "lt", {{"0", _Y("resource")}, {"1", _Y("2")}}, {{"decr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("resource"), 1); ASSERT_EQ(*dstObjectPtr->getVariableValue("resource"), 0); @@ -1154,8 +1097,8 @@ TEST(ObjectTest, command_gt) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "gt", {{"0", _Y("resource")}, {"1", _Y("0")}}, {{"incr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "gt", {{"0", _Y("resource")}, {"1", _Y("1")}}, {{"decr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("resource"), 2); ASSERT_EQ(*dstObjectPtr->getVariableValue("resource"), 1); @@ -1187,8 +1130,8 @@ TEST(ObjectTest, command_neq) { auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "neq", {{"0", _Y("resource")}, {"1", _Y("0")}}, {{"incr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "neq", {{"0", _Y("resource")}, {"1", _Y("1")}}, {{"decr", {{"0", _Y("resource")}}}}, srcObjectPtr, dstObjectPtr); - verifyCommandResult(srcResult, false, 0); - verifyCommandResult(dstResult, false, 0); + verifyCommandResult(srcResult, false, {}); + verifyCommandResult(dstResult, false, {}); ASSERT_EQ(*srcObjectPtr->getVariableValue("resource"), 2); ASSERT_EQ(*dstObjectPtr->getVariableValue("resource"), 1); @@ -1200,8 +1143,8 @@ TEST(ObjectTest, isValidAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {{"counter", _V(5)}}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); // auto mockActionPtr = std::shared_ptr(new MockAction()); @@ -1225,8 +1168,8 @@ TEST(ObjectTest, isValidActionNotDefinedForAction) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {{"counter", _V(5)}}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); @@ -1245,8 +1188,8 @@ TEST(ObjectTest, isValidActionNotDefinedForDestination) { auto srcObjectName = "srcObject"; auto dstObjectName = "dstObject"; auto actionName = "action"; - auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, 0, {{"counter", _V(5)}}, nullptr)); - auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, 0, {}, nullptr)); + auto srcObject = std::shared_ptr(new Object(srcObjectName, 0, 0, {{"counter", _V(5)}}, nullptr)); + auto dstObject = std::shared_ptr(new Object(dstObjectName, 0, 0, {}, nullptr)); auto mockActionPtr = setupAction(actionName, srcObject, dstObject); diff --git a/tests/src/Griddly/Core/GameProcessTest.cpp b/tests/src/Griddly/Core/GameProcessTest.cpp index 0d6f04245..b16699d34 100644 --- a/tests/src/Griddly/Core/GameProcessTest.cpp +++ b/tests/src/Griddly/Core/GameProcessTest.cpp @@ -572,7 +572,7 @@ TEST(GameProcessTest, performActions) { auto actionsList = std::vector>{mockActionPtr}; EXPECT_CALL(*mockGridPtr, performActions(Eq(playerId), Eq(actionsList))) - .WillOnce(Return(std::vector{5, 5, 4})); + .WillOnce(Return(std::unordered_map{{1, 14}})); EXPECT_CALL(*mockTerminationHandlerPtr, isTerminated()) .WillOnce(Return(TerminationResult{false, {}})); @@ -584,7 +584,7 @@ TEST(GameProcessTest, performActions) { ASSERT_FALSE(result.terminated); - ASSERT_THAT(result.rewards, ElementsAreArray({5, 5, 4})); + ASSERT_EQ(result.reward, 14); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); @@ -610,19 +610,19 @@ TEST(GameProcessTest, performActionsDelayedReward) { std::vector> actionList{mockActionPtr}; EXPECT_CALL(*mockGridPtr, performActions(Eq(1), Eq(actionList))) - .WillOnce(Return(std::vector{5, 5, 4})); + .WillOnce(Return(std::unordered_map{{1, 14}, {2, 3}})); EXPECT_CALL(*mockTerminationHandlerPtr, isTerminated) .WillOnce(Return(TerminationResult{false, {}})); EXPECT_CALL(*mockGridPtr, update()) - .WillOnce(Return(std::unordered_map{{1, 5}, {2, 6}})); + .WillOnce(Return(std::unordered_map{{1, 5}, {5, 3}})); auto result = gameProcessPtr->performActions(1, actionList); ASSERT_FALSE(result.terminated); - ASSERT_THAT(result.rewards, ElementsAreArray({5, 5, 4, 5})); + ASSERT_EQ(result.reward, 19); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); @@ -630,9 +630,9 @@ TEST(GameProcessTest, performActionsDelayedReward) { TEST(GameProcessTest, getAvailableActionNames) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - auto mockObject1 = mockObject("object", 0, 0, 0, {0, 1}, DiscreteOrientation(), {"move", "internal"}); - auto mockObject2 = mockObject("object", 1, 0, 0, {4, 6}, DiscreteOrientation(), {"move", "fire"}); - auto mockObject3 = mockObject("object", 1, 0, 0, {20, 13}, DiscreteOrientation(), {}); + auto mockObject1 = mockObject("object", 0, 0, {0, 1}, DiscreteOrientation(), {"move", "internal"}); + auto mockObject2 = mockObject("object", 1, 0, {4, 6}, DiscreteOrientation(), {"move", "fire"}); + auto mockObject3 = mockObject("object", 1, 0, {20, 13}, DiscreteOrientation(), {}); auto objects = std::unordered_set>{mockObject1, mockObject2, mockObject3}; @@ -703,7 +703,7 @@ TEST(GameProcessTest, getAvailableIdsForActionType) { auto mockGridPtr = std::shared_ptr(new MockGrid()); auto objectLocation = glm::ivec2{0, 1}; - auto mockObject1 = mockObject("object", 1, 0, 0, objectLocation, DiscreteOrientation(), {"move", "attack"}); + auto mockObject1 = mockObject("object", 1, 0, objectLocation, DiscreteOrientation(), {"move", "attack"}); EXPECT_CALL(*mockGridPtr, getObject(Eq(objectLocation))) .Times(2) @@ -764,9 +764,9 @@ TEST(GameProcessTest, getState) { auto globalVar = _V(5); auto playerVar = _V(6); - auto mockObject1 = mockObject("object1", 0, 0, 0, {0, 1}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param1", _V(20)}}); - auto mockObject2 = mockObject("object2", 1, 0, 0, {4, 6}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param2", _V(5)}}); - auto mockObject3 = mockObject("object3", 1, 0, 0, {20, 13}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param3", _V(12)}}); + auto mockObject1 = mockObject("object1", 0, 0, {0, 1}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param1", _V(20)}}); + auto mockObject2 = mockObject("object2", 1, 0, {4, 6}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param2", _V(5)}}); + auto mockObject3 = mockObject("object3", 1, 0, {20, 13}, DiscreteOrientation(), {}, {{"global_var", globalVar}, {"test_param3", _V(12)}}); auto objects = std::unordered_set>{mockObject1, mockObject2, mockObject3}; diff --git a/tests/src/Griddly/Core/GridTest.cpp b/tests/src/Griddly/Core/GridTest.cpp index b3f9e3755..50ab0edf3 100644 --- a/tests/src/Griddly/Core/GridTest.cpp +++ b/tests/src/Griddly/Core/GridTest.cpp @@ -7,6 +7,7 @@ #include "gtest/gtest.h" using ::testing::ElementsAre; +using ::testing::ContainerEq; using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::Mock; @@ -16,13 +17,14 @@ namespace griddly { MATCHER_P(ActionEventMatcher, expectedEvent, "") { auto actualEvent = arg; + return expectedEvent.actionName == arg.actionName && expectedEvent.sourceObjectName == arg.sourceObjectName && expectedEvent.destObjectName == arg.destObjectName && expectedEvent.sourceLocation == arg.sourceLocation && expectedEvent.destLocation == arg.destLocation && expectedEvent.tick == arg.tick && - expectedEvent.reward == arg.reward; + expectedEvent.rewards.size() == arg.rewards.size(); } TEST(GridTest, getHeightAndWidth) { @@ -117,8 +119,8 @@ TEST(GridTest, initializeObjectPositionTwiceDifferentZ) { auto grid = std::shared_ptr(new Grid()); grid->resetMap(123, 456); - auto mockObjectPtr = mockObject("object1", 1, 0, 0); - auto mockObjectPtr2 = mockObject("object2", 1, 0, 1); + auto mockObjectPtr = mockObject("object1", 1, 0); + auto mockObjectPtr2 = mockObject("object2", 1, 1); grid->initObject("object1", {}); grid->initObject("object2", {}); @@ -160,7 +162,7 @@ TEST(GridTest, removeObject) { auto playerId = 1; auto objectLocation = glm::ivec2(1, 2); - auto mockObjectPtr = mockObject("object", playerId, 0, 0, objectLocation); + auto mockObjectPtr = mockObject("object", playerId, 0, objectLocation); grid->initObject("object", {}); grid->addObject(objectLocation, mockObjectPtr); @@ -179,7 +181,7 @@ TEST(GridTest, removeObjectNotInitialized) { auto objectLocation = glm::ivec2{4, 4}; - auto mockObjectPtr = mockObject("object", 1, 0, 0, objectLocation); + auto mockObjectPtr = mockObject("object", 1, 0, objectLocation); grid->initObject("object", {}); ASSERT_EQ(grid->getObjects().size(), 0); @@ -203,8 +205,7 @@ TEST(GridTest, performActionOnEmptySpace) { auto reward = grid->performActions(1, actions); - ASSERT_EQ(reward.size(), 1); - ASSERT_THAT(reward, ElementsAre(0)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{})); GridEvent gridEvent; gridEvent.actionName = "action"; @@ -215,7 +216,7 @@ TEST(GridTest, performActionOnEmptySpace) { gridEvent.destObjectName = "_empty"; gridEvent.sourceLocation = mockActionSourceLocation; gridEvent.destLocation = mockActionDestinationLocation; - gridEvent.reward = 0; + gridEvent.rewards = {}; gridEvent.tick = 0; gridEvent.delay = 0; @@ -234,7 +235,7 @@ TEST(GridTest, performActionOnObjectWithNeutralPlayerId) { auto mockSourceObjectLocation = glm::ivec2(1, 0); auto actionDestinationLocation = glm::ivec2(2, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -249,7 +250,7 @@ TEST(GridTest, performActionOnObjectWithNeutralPlayerId) { auto reward = grid->performActions(playerId, actions); - ASSERT_THAT(reward, ElementsAre(0)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{})); GridEvent gridEvent; gridEvent.actionName = "action"; @@ -260,7 +261,7 @@ TEST(GridTest, performActionOnObjectWithNeutralPlayerId) { gridEvent.destObjectName = "_empty"; gridEvent.sourceLocation = mockSourceObjectLocation; gridEvent.destLocation = actionDestinationLocation; - gridEvent.reward = 0; + gridEvent.rewards = {}; gridEvent.tick = 0; gridEvent.delay = 0; @@ -278,7 +279,7 @@ TEST(GridTest, performActionOnObjectWithDifferentPlayerId) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(1, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -292,7 +293,7 @@ TEST(GridTest, performActionOnObjectWithDifferentPlayerId) { auto reward = grid->performActions(playerId, actions); - ASSERT_THAT(reward, ElementsAre(0)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{})); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockSourceObjectPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockActionPtr.get())); @@ -308,7 +309,7 @@ TEST(GridTest, performActionDestinationObjectNull) { auto mockSourceObjectLocation = glm::ivec2(1, 0); auto actionDestinationLocation = glm::ivec2(2, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -317,7 +318,7 @@ TEST(GridTest, performActionDestinationObjectNull) { EXPECT_CALL(*mockSourceObjectPtr, onActionSrc(Eq("_empty"), Eq(mockActionPtr))) .Times(1) - .WillOnce(Return(BehaviourResult{false, 5})); + .WillOnce(Return(BehaviourResult{false, {{3, 5}}})); auto actions = std::vector>{mockActionPtr}; @@ -327,7 +328,7 @@ TEST(GridTest, performActionDestinationObjectNull) { auto reward = grid->performActions(playerId, actions); - ASSERT_THAT(reward, ElementsAre(5)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{{3,5}})); GridEvent gridEvent; gridEvent.actionName = "action"; @@ -338,7 +339,7 @@ TEST(GridTest, performActionDestinationObjectNull) { gridEvent.destObjectName = "_empty"; gridEvent.sourceLocation = mockSourceObjectLocation; gridEvent.destLocation = actionDestinationLocation; - gridEvent.reward = 5; + gridEvent.rewards = {{3,5}}; gridEvent.tick = 0; gridEvent.delay = 0; @@ -357,12 +358,12 @@ TEST(GridTest, performActionCannotBePerformedOnDestinationObject) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); uint32_t mockDestinationObjectPlayerId = 2; auto mockDestinationObjectLocation = glm::ivec2(0, 1); - auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); + auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, mockDestinationObjectLocation); grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -376,7 +377,7 @@ TEST(GridTest, performActionCannotBePerformedOnDestinationObject) { EXPECT_CALL(*mockDestinationObjectPtr, onActionDst) .Times(1) - .WillOnce(Return(BehaviourResult{true, 0})); + .WillOnce(Return(BehaviourResult{true})); EXPECT_CALL(*mockSourceObjectPtr, onActionSrc) .Times(0); @@ -385,7 +386,7 @@ TEST(GridTest, performActionCannotBePerformedOnDestinationObject) { auto reward = grid->performActions(playerId, actions); - ASSERT_THAT(reward, ElementsAre(0)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{})); GridEvent gridEvent; gridEvent.actionName = "action"; @@ -396,7 +397,7 @@ TEST(GridTest, performActionCannotBePerformedOnDestinationObject) { gridEvent.destObjectName = "dstObject"; gridEvent.sourceLocation = mockSourceObjectLocation; gridEvent.destLocation = mockDestinationObjectLocation; - gridEvent.reward = 0; + gridEvent.rewards = {}; gridEvent.tick = 0; gridEvent.delay = 0; @@ -415,12 +416,12 @@ TEST(GridTest, performActionCanBePerformedOnDestinationObject) { uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); - uint32_t mockDestinationObjectPlayerId = 2; + uint32_t mockDestinationObjectPlayerId = 4; auto mockDestinationObjectLocation = glm::ivec2(0, 1); - auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); + auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, mockDestinationObjectLocation); grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -434,17 +435,17 @@ TEST(GridTest, performActionCanBePerformedOnDestinationObject) { EXPECT_CALL(*mockDestinationObjectPtr, onActionDst) .Times(1) - .WillOnce(Return(BehaviourResult{false, 5})); + .WillOnce(Return(BehaviourResult{false, {{2, 5}}})); EXPECT_CALL(*mockSourceObjectPtr, onActionSrc(Eq("dstObject"), Eq(mockActionPtr))) .Times(1) - .WillOnce(Return(BehaviourResult{false, 5})); + .WillOnce(Return(BehaviourResult{false, {{4, 5}}})); auto actions = std::vector>{mockActionPtr}; auto reward = grid->performActions(playerId, actions); - ASSERT_THAT(reward, ElementsAre(10)); + ASSERT_THAT(reward, ContainerEq(std::unordered_map{{2, 5}, {4, 5}})); GridEvent gridEvent; gridEvent.actionName = "action"; @@ -455,7 +456,7 @@ TEST(GridTest, performActionCanBePerformedOnDestinationObject) { gridEvent.destObjectName = "dstObject"; gridEvent.sourceLocation = mockSourceObjectLocation; gridEvent.destLocation = mockDestinationObjectLocation; - gridEvent.reward = 10; + gridEvent.rewards = {{2, 5}, {4, 5}}; gridEvent.tick = 0; gridEvent.delay = 0; @@ -472,14 +473,12 @@ TEST(GridTest, performActionDelayed) { uint32_t playerId = 2; - uint32_t mockSourceObjectPlayerId = 2; auto mockSourceObjectLocation = glm::ivec2(0, 0); - auto mockSourceObjectPtr = mockObject("srcObject", mockSourceObjectPlayerId, 0, 0, mockSourceObjectLocation); + auto mockSourceObjectPtr = mockObject("srcObject", playerId, 0, mockSourceObjectLocation); grid->initObject("srcObject", {}); - uint32_t mockDestinationObjectPlayerId = 2; auto mockDestinationObjectLocation = glm::ivec2(0, 1); - auto mockDestinationObjectPtr = mockObject("dstObject", mockDestinationObjectPlayerId, 0, 0, mockDestinationObjectLocation); + auto mockDestinationObjectPtr = mockObject("dstObject", playerId, 0, mockDestinationObjectLocation); grid->initObject("dstObject", {}); grid->addObject(mockSourceObjectLocation, mockSourceObjectPtr); @@ -497,11 +496,11 @@ TEST(GridTest, performActionDelayed) { EXPECT_CALL(*mockDestinationObjectPtr, onActionDst) .Times(1) - .WillOnce(Return(BehaviourResult{false, 5})); + .WillOnce(Return(BehaviourResult{false, {{playerId, 5}}})); EXPECT_CALL(*mockSourceObjectPtr, onActionSrc(Eq("dstObject"), Eq(mockActionPtr))) .Times(1) - .WillOnce(Return(BehaviourResult{false, 6})); + .WillOnce(Return(BehaviourResult{false, {{playerId, 6}}})); auto actions = std::vector>{mockActionPtr}; @@ -520,18 +519,20 @@ TEST(GridTest, performActionDelayed) { // on the 10th tick we recieve the reward as the action is executed auto delayedRewards = grid->update(); ASSERT_EQ(delayedRewards.size(), 1); - ASSERT_EQ(delayedRewards[playerId], 11); + + std::unordered_map expectedRewards{{playerId, 11}}; + ASSERT_EQ(delayedRewards, expectedRewards); GridEvent gridEvent; gridEvent.actionName = "action"; gridEvent.playerId = 2; - gridEvent.sourceObjectPlayerId = mockSourceObjectPlayerId; - gridEvent.destinationObjectPlayerId = mockDestinationObjectPlayerId; + gridEvent.sourceObjectPlayerId = 2; + gridEvent.destinationObjectPlayerId = 2; gridEvent.sourceObjectName = "srcObject"; gridEvent.destObjectName = "dstObject"; gridEvent.sourceLocation = mockSourceObjectLocation; gridEvent.destLocation = mockDestinationObjectLocation; - gridEvent.reward = 11; + gridEvent.rewards = {{playerId, 11}}; gridEvent.tick = 10; gridEvent.delay = 10; diff --git a/tests/src/Griddly/Core/Observers/ObserverRTSTestData.hpp b/tests/src/Griddly/Core/Observers/ObserverRTSTestData.hpp index aeec65626..ead6cb80d 100644 --- a/tests/src/Griddly/Core/Observers/ObserverRTSTestData.hpp +++ b/tests/src/Griddly/Core/Observers/ObserverRTSTestData.hpp @@ -15,6 +15,8 @@ using ::testing::ReturnRef; namespace griddly { +#define _V(X) std::make_shared(X) + class ObserverRTSTestData { public: ObserverRTSTestData(ObserverConfig observerConfig) { @@ -25,16 +27,31 @@ class ObserverRTSTestData { // 1 A3 B1 C2 1 // 1 1 1 1 1 - mockObjectWallPtr = mockObject("W", 0, 3); - mockObjectA1Ptr = mockObject("A", 1, 0); - mockObjectA2Ptr = mockObject("A", 2, 0); - mockObjectA3Ptr = mockObject("A", 3, 0); - mockObjectB1Ptr = mockObject("B", 1, 1); - mockObjectB2Ptr = mockObject("B", 2, 1); - mockObjectB3Ptr = mockObject("B", 3, 1); - mockObjectC1Ptr = mockObject("C", 1, 2); - mockObjectC2Ptr = mockObject("C", 2, 2); - mockObjectC3Ptr = mockObject("C", 3, 2); + if(observerConfig.includeVariables) { + mockObjectWallPtr = mockObject("W", 0, 0, {0,0}, DiscreteOrientation(), {}, {}); + mockObjectA1Ptr = mockObject("A", 1, 0, {0,0}, DiscreteOrientation(), {}, {{"V1", _V(1)}, {"_ignored", _V(10)}}); + mockObjectA2Ptr = mockObject("A", 2, 0, {0,0}, DiscreteOrientation(), {}, {{"V2", _V(2)}, {"_ignored", _V(10)}}); + mockObjectA3Ptr = mockObject("A", 3, 0, {0,0}, DiscreteOrientation(), {}, {{"V3", _V(3)}, {"_ignored", _V(10)}}); + mockObjectB1Ptr = mockObject("B", 1, 0, {0,0}, DiscreteOrientation(), {}, {{"V1", _V(4)}, {"_ignored", _V(10)}}); + mockObjectB2Ptr = mockObject("B", 2, 0, {0,0}, DiscreteOrientation(), {}, {{"V2", _V(5)}, {"_ignored", _V(10)}}); + mockObjectB3Ptr = mockObject("B", 3, 0, {0,0}, DiscreteOrientation(), {}, {{"V3", _V(6)}, {"_ignored", _V(10)}}); + mockObjectC1Ptr = mockObject("C", 1, 0, {0,0}, DiscreteOrientation(), {}, {{"V1", _V(7)}, {"_ignored", _V(10)}}); + mockObjectC2Ptr = mockObject("C", 2, 0, {0,0}, DiscreteOrientation(), {}, {{"V2", _V(8)}, {"_ignored", _V(10)}}); + mockObjectC3Ptr = mockObject("C", 3, 0, {0,0}, DiscreteOrientation(), {}, {{"V3", _V(9)}, {"_ignored", _V(10)}}); + } else { + mockObjectWallPtr = mockObject("W", 0); + mockObjectA1Ptr = mockObject("A", 1); + mockObjectA2Ptr = mockObject("A", 2); + mockObjectA3Ptr = mockObject("A", 3); + mockObjectB1Ptr = mockObject("B", 1); + mockObjectB2Ptr = mockObject("B", 2); + mockObjectB3Ptr = mockObject("B", 3); + mockObjectC1Ptr = mockObject("C", 1); + mockObjectC2Ptr = mockObject("C", 2); + mockObjectC3Ptr = mockObject("C", 3); + } + + mockRTSObjects = std::unordered_set>{ mockObjectWallPtr, @@ -87,9 +104,11 @@ class ObserverRTSTestData { .WillRepeatedly(Return(5)); EXPECT_CALL(*mockGridPtr, getObjects()).WillRepeatedly(ReturnRef(mockRTSObjects)); - EXPECT_CALL(*mockGridPtr, getUniqueObjectCount).WillRepeatedly(Return(4)); EXPECT_CALL(*mockGridPtr, getUpdatedLocations).WillRepeatedly(ReturnRef(mockRTSUpdatedLocations)); + EXPECT_CALL(*mockGridPtr, getObjectVariableIds()).WillRepeatedly(ReturnRef(mockObjectVariableIds)); + EXPECT_CALL(*mockGridPtr, getObjectIds()).WillRepeatedly(ReturnRef(mockSinglePlayerObjectIds)); + bool hasOffsets = observerConfig.gridXOffset != 0 || observerConfig.gridYOffset != 0; if (!hasOffsets) { @@ -117,6 +136,19 @@ class ObserverRTSTestData { std::shared_ptr mockGridPtr; + const std::unordered_map mockSinglePlayerObjectIds = { + {"W", 3}, + {"A", 0}, + {"B", 1}, + {"C", 2} + }; + + const std::unordered_map mockObjectVariableIds = { + {"V1", 0}, + {"V2", 1}, + {"V3", 2} + }; + std::shared_ptr mockObjectWallPtr; std::shared_ptr mockObjectA1Ptr; std::shared_ptr mockObjectA2Ptr; @@ -129,7 +161,6 @@ class ObserverRTSTestData { std::shared_ptr mockObjectC3Ptr; std::unordered_set> mockRTSObjects; - std::unordered_map mockRTSGridData; const std::unordered_set mockRTSUpdatedLocations = { diff --git a/tests/src/Griddly/Core/Observers/ObserverTestData.hpp b/tests/src/Griddly/Core/Observers/ObserverTestData.hpp index 89b1c1386..ba5ecaea7 100644 --- a/tests/src/Griddly/Core/Observers/ObserverTestData.hpp +++ b/tests/src/Griddly/Core/Observers/ObserverTestData.hpp @@ -18,10 +18,10 @@ namespace griddly { class ObserverTestData { public: ObserverTestData(ObserverConfig observerConfig, DiscreteOrientation orientation, bool trackAvatar) { - mockAvatarObjectPtr = mockObject("avatar", 1, 3, 0, {2, 2}); - mockSinglePlayerObject1Ptr = mockObject("mo1", 1, 0); - mockSinglePlayerObject2Ptr = mockObject("mo2", 1, 1); - mockSinglePlayerObject3Ptr = mockObject("mo3", 1, 2); + mockAvatarObjectPtr = mockObject("avatar", 1, 0, {2, 2}); + mockSinglePlayerObject1Ptr = mockObject("mo1", 1); + mockSinglePlayerObject2Ptr = mockObject("mo2", 1); + mockSinglePlayerObject3Ptr = mockObject("mo3", 1); mockSinglePlayerObjects = {mockSinglePlayerObject1Ptr, mockSinglePlayerObject2Ptr, mockSinglePlayerObject3Ptr}; mockSinglePlayerGridData = { @@ -63,8 +63,8 @@ class ObserverTestData { .WillRepeatedly(Return(5)); EXPECT_CALL(*mockGridPtr, getObjects()).WillRepeatedly(ReturnRef(mockSinglePlayerObjects)); - EXPECT_CALL(*mockGridPtr, getUniqueObjectCount).WillRepeatedly(Return(4)); EXPECT_CALL(*mockGridPtr, getUpdatedLocations).WillRepeatedly(ReturnRef(mockSinglePlayerUpdatedLocations)); + EXPECT_CALL(*mockGridPtr, getObjectIds()).WillRepeatedly(ReturnRef(mockSinglePlayerObjectIds)); bool hasOffsets = observerConfig.gridXOffset != 0 || observerConfig.gridYOffset != 0; @@ -101,9 +101,15 @@ class ObserverTestData { std::shared_ptr mockSinglePlayerObject3Ptr; std::unordered_set> mockSinglePlayerObjects; - std::unordered_map mockSinglePlayerGridData; + const std::unordered_map mockSinglePlayerObjectIds = { + {"avatar", 3}, + {"mo1", 0}, + {"mo2", 1}, + {"mo3", 2} + }; + const std::unordered_set mockSinglePlayerUpdatedLocations = { {0, 0}, {0, 1}, diff --git a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp index 000639822..181ffe814 100644 --- a/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp +++ b/tests/src/Griddly/Core/Observers/VectorObserverTest.cpp @@ -551,4 +551,40 @@ TEST(VectorObserverTest, multiPlayer_Outline_Global) { runVectorObserverRTSTest(config, {8, 5, 5}, {1, 8, 8 * 5}, expectedData[0][0]); } +TEST(VectorObserverTest, multiPlayer_PlusVariables_Player1) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 1; + config.playerCount = 3; + + config.includePlayerId = true; + config.includeVariables = true; + + uint8_t expectedData[5][5][11] = { + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 1, 0, 0, 4, 0, 0}, {0, 0, 1, 0, 0, 1, 0, 0, 7, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0}, {0, 1, 0, 0, 0, 0, 1, 0, 0, 5, 0}, {0, 0, 1, 0, 0, 0, 1, 0, 0, 8, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3}, {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 6}, {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 9}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {11, 5, 5}, {1, 11, 11 * 5}, expectedData[0][0]); +} + +TEST(VectorObserverTest, multiPlayer_PlusVariables_Player2) { + ObserverConfig config = {5, 5, 0, 0}; + config.playerId = 2; + config.playerCount = 3; + + config.includePlayerId = true; + config.includeVariables = true; + + uint8_t expectedData[5][5][11] = { + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0}, {0, 1, 0, 0, 0, 0, 1, 0, 4, 0, 0}, {0, 0, 1, 0, 0, 0, 1, 0, 7, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0}, {0, 1, 0, 0, 0, 1, 0, 0, 0, 5, 0}, {0, 0, 1, 0, 0, 1, 0, 0, 0, 8, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3}, {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 6}, {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 9}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}}}; + + runVectorObserverRTSTest(config, {11, 5, 5}, {1, 11, 11 * 5}, expectedData[0][0]); +} + } // namespace griddly \ No newline at end of file diff --git a/tests/src/Griddly/Core/Players/PlayerTest.cpp b/tests/src/Griddly/Core/Players/PlayerTest.cpp index a55a15771..d060c221a 100644 --- a/tests/src/Griddly/Core/Players/PlayerTest.cpp +++ b/tests/src/Griddly/Core/Players/PlayerTest.cpp @@ -44,13 +44,13 @@ TEST(PlayerTest, performActions) { EXPECT_CALL(*mockGameProcessPtr, performActions(Eq(playerId), Eq(actionsList), Eq(true))) .Times(1) - .WillOnce(Return(ActionResult{{}, false, std::vector{0, 1, 2, 3, 4}})); + .WillOnce(Return(ActionResult{{}, false, 10})); auto actionResult = player->performActions(actionsList); - auto rewards = actionResult.rewards; + auto reward = actionResult.reward; auto terminated = actionResult.terminated; - ASSERT_THAT(rewards, ElementsAre(0,1,2,3,4)); + ASSERT_THAT(reward, 10); EXPECT_EQ(*player->getScore(), 10); EXPECT_FALSE(terminated); @@ -74,14 +74,14 @@ TEST(PlayerTest, performActions_terminated) { EXPECT_CALL(*mockGameProcessPtr, performActions(Eq(playerId), Eq(actionsList), Eq(true))) .Times(1) - .WillOnce(Return(ActionResult{{{1, TerminationState::WIN}}, true, std::vector{0, 1, 2, 3, 4}})); + .WillOnce(Return(ActionResult{{{1, TerminationState::WIN}}, true, 10})); auto actionResult = player->performActions(actionsList); - auto rewards = actionResult.rewards; + auto reward = actionResult.reward; auto terminated = actionResult.terminated; auto states = actionResult.playerStates; - ASSERT_THAT(rewards, ElementsAre(0,1,2,3,4)); + ASSERT_THAT(reward, 10); EXPECT_EQ(*player->getScore(), 10); EXPECT_EQ(states, (std::unordered_map{{1, TerminationState::WIN}})); EXPECT_TRUE(terminated); diff --git a/tests/src/Griddly/Core/TestUtils/common.hpp b/tests/src/Griddly/Core/TestUtils/common.hpp index 52d86f44c..87f558d84 100644 --- a/tests/src/Griddly/Core/TestUtils/common.hpp +++ b/tests/src/Griddly/Core/TestUtils/common.hpp @@ -15,11 +15,10 @@ using ::testing::Return; namespace griddly { -std::shared_ptr static mockObject(std::string objectName = "object", uint32_t playerId = 1, uint32_t objectId = 0, uint32_t zidx = 0, glm::ivec2 location = {0, 0}, DiscreteOrientation orientation = DiscreteOrientation(), std::unordered_set availableActionNames = {}, std::unordered_map> availableVariables = {}) { +std::shared_ptr static mockObject(std::string objectName = "object", uint32_t playerId = 1, uint32_t zidx = 0, glm::ivec2 location = {0, 0}, DiscreteOrientation orientation = DiscreteOrientation(), std::unordered_set availableActionNames = {}, std::unordered_map> availableVariables = {}) { auto mockObjectPtr = std::shared_ptr(new MockObject()); EXPECT_CALL(*mockObjectPtr, getPlayerId()).WillRepeatedly(Return(playerId)); - EXPECT_CALL(*mockObjectPtr, getObjectId()).WillRepeatedly(Return(objectId)); EXPECT_CALL(*mockObjectPtr, getObjectName()).WillRepeatedly(Return(objectName)); EXPECT_CALL(*mockObjectPtr, getObjectOrientation()).WillRepeatedly(Return(orientation)); EXPECT_CALL(*mockObjectPtr, getObjectRenderTileName()).WillRepeatedly(Return(objectName + std::to_string(0))); diff --git a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp index 34f005f26..4140c7671 100644 --- a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp +++ b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp @@ -7,9 +7,8 @@ namespace griddly { class MockObject : public Object { public: - MockObject() - : Object("mockObject", 0, 0, 0, {}, nullptr) { + : Object("mockObject", 0, 0, {}, nullptr) { } MOCK_METHOD(void, init, (glm::ivec2 location, std::shared_ptr grid_), ()); @@ -18,10 +17,9 @@ class MockObject : public Object { MOCK_METHOD(glm::ivec2, getLocation, (), (const)); MOCK_METHOD(std::string, getObjectName, (), (const)); MOCK_METHOD(std::string, getObjectRenderTileName, (), (const)); - MOCK_METHOD(uint32_t, getObjectId, (), (const)); MOCK_METHOD(uint32_t, getPlayerId, (), (const)); MOCK_METHOD(std::string, getDescription, (), (const)); - MOCK_METHOD(DiscreteOrientation, getObjectOrientation, (), (const)); + MOCK_METHOD(DiscreteOrientation, getObjectOrientation, (), (const)); MOCK_METHOD(bool, isPlayerAvatar, (), (const)); @@ -37,6 +35,5 @@ class MockObject : public Object { MOCK_METHOD(void, addActionSrcBehaviour, (std::string action, std::string destinationObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (std::unordered_map conditionalCommands)), (override)); MOCK_METHOD(void, addActionDstBehaviour, (std::string action, std::string sourceObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (std::unordered_map conditionalCommands)), (override)); - }; } // namespace griddly \ No newline at end of file diff --git a/tests/src/Mocks/Griddly/Core/MockGrid.hpp b/tests/src/Mocks/Griddly/Core/MockGrid.hpp index 0378fcd30..8c063b32b 100644 --- a/tests/src/Mocks/Griddly/Core/MockGrid.hpp +++ b/tests/src/Mocks/Griddly/Core/MockGrid.hpp @@ -20,14 +20,18 @@ class MockGrid : public Grid { MOCK_METHOD(uint32_t, getHeight, (), (const)); MOCK_METHOD(bool, updateLocation, (std::shared_ptr object, glm::ivec2 previousLocation, glm::ivec2 newLocation), ()); - MOCK_METHOD(std::vector, performActions, (uint32_t playerId, std::vector> actions), ()); + MOCK_METHOD((std::unordered_map), performActions, (uint32_t playerId, std::vector> actions), ()); MOCK_METHOD(void, initObject, (std::string, std::vector), ()); MOCK_METHOD(void, addObject, (glm::ivec2 location, std::shared_ptr object, bool applyInitialActions), ()); MOCK_METHOD(bool, removeObject, (std::shared_ptr object), ()); MOCK_METHOD((std::unordered_map>), getObjectCounter, (std::string), ()); - MOCK_METHOD(uint32_t, getUniqueObjectCount, (), (const)); + + MOCK_METHOD((const std::unordered_map&), getObjectIds, (), (const)); + MOCK_METHOD((const std::unordered_map&), getObjectVariableIds, (), (const)); + MOCK_METHOD((const std::vector), getObjectVariableNames, (), (const)); + MOCK_METHOD((const std::vector), getObjectNames, (), (const)); MOCK_METHOD((const std::unordered_map>>&), getGlobalVariables, (), (const)); From 609a3270f904f000e5619ea68ac37e420f3a027c Mon Sep 17 00:00:00 2001 From: Bam4d Date: Fri, 19 Feb 2021 20:25:54 +0000 Subject: [PATCH 12/34] more bug fixes for multi agent and dog food --- .../griddlyrts/griddly_rts_global.png | Bin 895 -> 905 bytes python/examples/griddlyrts/griddly_rts_p1.png | Bin 895 -> 905 bytes python/examples/griddlyrts/griddly_rts_p2.png | Bin 895 -> 905 bytes python/examples/gym/gym_human_player.py | 4 +- .../rllib/rllib_multiagent_grouped.py | 86 --------- .../rllib/rllib_multiagent_taggers.py | 61 +++++++ python/examples/rllib/rllib_single_agent.py | 18 +- ...{test_ma_grouped.yaml => test_ma_tag.yaml} | 78 +++++++-- python/examples/snippet.py | 54 ++++-- python/examples/vectorized.py | 25 --- python/tests/history_test.py | 17 +- python/tests/step_test.py | 28 +-- src/Griddly/Core/GDY/GDYFactory.cpp | 1 + src/Griddly/Core/GameProcess.cpp | 12 +- src/Griddly/Core/GameProcess.hpp | 3 + src/Griddly/Core/Grid.cpp | 12 ++ src/Griddly/Core/Observers/VectorObserver.cpp | 5 + src/Griddly/Core/TurnBasedGameProcess.cpp | 33 ++-- tests/src/Griddly/Core/GameProcessTest.cpp | 164 +++++++++++++++++- tests/src/Mocks/Griddly/Core/MockGrid.hpp | 1 + 20 files changed, 397 insertions(+), 205 deletions(-) delete mode 100644 python/examples/rllib/rllib_multiagent_grouped.py create mode 100644 python/examples/rllib/rllib_multiagent_taggers.py rename python/examples/rllib/{test_ma_grouped.yaml => test_ma_tag.yaml} (73%) delete mode 100644 python/examples/vectorized.py diff --git a/python/examples/griddlyrts/griddly_rts_global.png b/python/examples/griddlyrts/griddly_rts_global.png index 92dffddaf8577bafab91b14df23ca3aa09c5e138..17f72846a5440c1217c0c7dd164e6bbdd3d652c7 100644 GIT binary patch literal 905 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_tH;PZ!6Kid%2*dgo~wins>K zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXK zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXK zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIXsetPlayerCount(gdyFactory_->getPlayerCount()); + grid_->setPlayerCount(playerCount); grid_->resetGlobalVariables(gdyFactory_->getGlobalVariableDefinitions()); @@ -95,7 +95,7 @@ void GameProcess::init(bool isCloned) { throw std::invalid_argument(errorString); } - for (auto &p : players_) { + for (auto& p : players_) { spdlog::debug("Initializing player Name={0}, Id={1}", p->getName(), p->getId()); ObserverConfig observerConfig = getObserverConfig(p->getObserver()->getObserverType()); @@ -127,7 +127,7 @@ void GameProcess::init(bool isCloned) { uint8_t* GameProcess::resetObservers() { auto playerAvatarObjects = grid_->getPlayerAvatarObjects(); - for (auto &p : players_) { + for (auto& p : players_) { p->reset(); if (playerAvatarObjects.size() > 0) { p->setAvatar(playerAvatarObjects.at(p->getId())); @@ -135,7 +135,7 @@ uint8_t* GameProcess::resetObservers() { } if (observer_ == nullptr) { - return nullptr; + return nullptr; } return observer_->reset(); @@ -156,6 +156,8 @@ uint8_t* GameProcess::reset() { terminationHandler_ = std::shared_ptr(gdyFactory_->createTerminationHandler(grid_, players_)); + requiresReset_ = false; + return observation; } @@ -177,7 +179,7 @@ ObserverConfig GameProcess::getObserverConfig(ObserverType observerType) const { void GameProcess::release() { spdlog::warn("Forcing release of vulkan"); observer_->release(); - for (auto &p : players_) { + for (auto& p : players_) { p->getObserver()->release(); } } diff --git a/src/Griddly/Core/GameProcess.hpp b/src/Griddly/Core/GameProcess.hpp index 4f36aa643..0c68ecc11 100644 --- a/src/Griddly/Core/GameProcess.hpp +++ b/src/Griddly/Core/GameProcess.hpp @@ -94,6 +94,9 @@ class GameProcess : public std::enable_shared_from_this { // Should the game process reset itself or rely on external reset bool autoReset_ = false; + // track whether this environment has finished or not, if it requires a reset, we can reset it + bool requiresReset_ = true; + private: uint8_t* resetObservers(); ObserverConfig getObserverConfig(ObserverType observerType) const; diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 28ada11d3..8ec9f12e1 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -154,6 +154,11 @@ std::unordered_map Grid::executeAction(uint32_t playerId, std return {}; } + if (playerId != 0 && sourceObject->isPlayerAvatar() && playerAvatars_.find(playerId) == playerAvatars_.end()) { + spdlog::debug("Avatar for player {0} has been removed, action will be ignored.", playerId); + return {}; + } + if (sourceObject->isValidAction(action)) { std::unordered_map rewardAccumulator; if (destinationObject != nullptr && destinationObject.get() != sourceObject.get()) { @@ -423,6 +428,13 @@ bool Grid::removeObject(std::shared_ptr object) { if (objects_.erase(object) > 0 && occupiedLocations_[location].erase(objectZIdx) > 0) { *objectCounters_[objectName][playerId] -= 1; invalidateLocation(location); + + // if we are removing a player's avatar + if(playerAvatars_[playerId] == object) { + spdlog::debug("Removing player {0} avatar {1}", playerId, objectName); + playerAvatars_.erase(playerId); + } + return true; } else { spdlog::error("Could not remove object={0} from environment.", object->getDescription()); diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index 1de98a81b..ede057b82 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -36,16 +36,20 @@ void VectorObserver::resetShape() { if (observerConfig_.includePlayerId) { channelsBeforePlayerCount_ = observationChannels_; observationChannels_ += observerConfig_.playerCount + 1; // additional one-hot for "no-player" + + spdlog::debug("Adding {0} playerId channels at: {1}", observationChannels_-channelsBeforePlayerCount_, channelsBeforePlayerCount_); } if (observerConfig_.includeRotation) { channelsBeforeRotation_ = observationChannels_; observationChannels_ += 4; + spdlog::debug("Adding {0} rotation channels at: {1}", observationChannels_-channelsBeforeRotation_, channelsBeforeRotation_); } if (observerConfig_.includeVariables) { channelsBeforeVariables_ = observationChannels_; observationChannels_ += grid_->getObjectVariableIds().size(); + spdlog::debug("Adding {0} variable channels at: {1}", observationChannels_-channelsBeforeVariables_, channelsBeforeVariables_); } observationShape_ = {observationChannels_, gridWidth_, gridHeight_}; @@ -127,6 +131,7 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output auto variableMemPtr = memPtr + channelsBeforeVariables_ + variableIdx; *variableMemPtr = variableValue; + } } } diff --git a/src/Griddly/Core/TurnBasedGameProcess.cpp b/src/Griddly/Core/TurnBasedGameProcess.cpp index 93b1dbe8f..81f70671f 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.cpp +++ b/src/Griddly/Core/TurnBasedGameProcess.cpp @@ -21,10 +21,19 @@ TurnBasedGameProcess::~TurnBasedGameProcess() { ActionResult TurnBasedGameProcess::performActions(uint32_t playerId, std::vector> actions, bool updateTicks) { spdlog::debug("Performing turn based actions for player {0}", playerId); - auto stepRewards = grid_->performActions(playerId, actions); + if(requiresReset_) { + throw std::runtime_error("Environment is in a terminated state and requires resetting."); + } + + std::unordered_map terminationState; int32_t reward = 0; + auto stepRewards = grid_->performActions(playerId, actions); + + // rewards resulting from player actions + accumulateRewards(accumulatedRewards_, stepRewards); + if (updateTicks) { spdlog::debug("Updating Grid"); auto delayedRewards = grid_->update(); @@ -32,27 +41,23 @@ ActionResult TurnBasedGameProcess::performActions(uint32_t playerId, std::vector // rewards could come from delayed actions that are run at a particular time step accumulateRewards(accumulatedRewards_, delayedRewards); - // rewards resulting from player actions - accumulateRewards(accumulatedRewards_, stepRewards); - - if (accumulatedRewards_[playerId] > 0) { - reward = accumulatedRewards_[playerId]; - // reset reward for this player as they are being returned here - accumulatedRewards_[playerId] = 0; - } - auto terminationResult = terminationHandler_->isTerminated(); - auto episodeComplete = terminationResult.terminated; + terminationState = terminationResult.playerStates; + requiresReset_ = terminationResult.terminated; - if (episodeComplete && autoReset_) { + if (requiresReset_ && autoReset_) { reset(); } + } - return {terminationResult.playerStates, episodeComplete, reward}; + if (accumulatedRewards_[playerId] != 0) { + reward = accumulatedRewards_[playerId]; + // reset reward for this player as they are being returned here + accumulatedRewards_[playerId] = 0; } - return {{}, false, reward}; + return {terminationState, requiresReset_, reward}; } // This is only used in tests diff --git a/tests/src/Griddly/Core/GameProcessTest.cpp b/tests/src/Griddly/Core/GameProcessTest.cpp index b16699d34..6ecb243e5 100644 --- a/tests/src/Griddly/Core/GameProcessTest.cpp +++ b/tests/src/Griddly/Core/GameProcessTest.cpp @@ -13,6 +13,7 @@ #define _V(X) std::make_shared(X) +using ::testing::_; using ::testing::AnyNumber; using ::testing::ElementsAre; using ::testing::ElementsAreArray; @@ -558,14 +559,41 @@ TEST(GameProcessTest, performActions) { std::unordered_map>> globalVariables{}; EXPECT_CALL(*mockGridPtr, getTickCount()) - .WillOnce(Return(std::make_shared(0))); + .WillRepeatedly(Return(std::make_shared(0))); EXPECT_CALL(*mockGridPtr, getGlobalVariables()) - .WillOnce(ReturnRef(globalVariables)); + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockGridPtr, getPlayerAvatarObjects()) + .WillRepeatedly(Return(std::unordered_map>{})); + EXPECT_CALL(*mockGridPtr, resetGlobalVariables(_)) + .Times(2); + auto mockLevelGeneratorPtr = std::shared_ptr(new MockLevelGenerator()); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); - auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, nullptr, mockGridPtr)); + auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + + EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) + .Times(2); + + EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + .WillRepeatedly(Return(mockLevelGeneratorPtr)); + EXPECT_CALL(*mockGDYFactory, getPlayerCount) + .WillRepeatedly(Return(1)); + EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + .WillRepeatedly(Return(PlayerObserverDefinition{})); + EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + .WillRepeatedly(Return(mockTerminationHandlerPtr)); + EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + .WillRepeatedly(Return(std::unordered_map{})); - gameProcessPtr->setTerminationHandler(mockTerminationHandlerPtr); + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); + + auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); + auto mockPlayerPtr = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); + + gameProcessPtr->addPlayer(mockPlayerPtr); + + gameProcessPtr->init(); + gameProcessPtr->reset(); auto mockActionPtr = std::shared_ptr(new MockAction()); @@ -590,20 +618,140 @@ TEST(GameProcessTest, performActions) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); } +TEST(GameProcessTest, performActionsMultiAgentRewards) { + auto mockGridPtr = std::shared_ptr(new MockGrid()); + + uint32_t player1Id = 1; + uint32_t player2Id = 2; + uint32_t player3Id = 3; + + std::unordered_map>> globalVariables{}; + + EXPECT_CALL(*mockGridPtr, getTickCount()) + .WillRepeatedly(Return(std::make_shared(0))); + EXPECT_CALL(*mockGridPtr, getGlobalVariables()) + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockGridPtr, getPlayerAvatarObjects()) + .WillRepeatedly(Return(std::unordered_map>{})); + EXPECT_CALL(*mockGridPtr, resetGlobalVariables(_)) + .Times(2); + + auto mockLevelGeneratorPtr = std::shared_ptr(new MockLevelGenerator()); + auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); + auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + + EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) + .Times(2); + + EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + .WillRepeatedly(Return(mockLevelGeneratorPtr)); + EXPECT_CALL(*mockGDYFactory, getPlayerCount) + .WillRepeatedly(Return(3)); + EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + .WillRepeatedly(Return(PlayerObserverDefinition{})); + EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + .WillRepeatedly(Return(mockTerminationHandlerPtr)); + EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + .WillRepeatedly(Return(std::unordered_map{})); + + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); + + auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); + auto mockPlayerPtr1 = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); + auto mockPlayerPtr2 = mockPlayer("Spiff", 2, gameProcessPtr, nullptr, mockObserverPtr); + auto mockPlayerPtr3 = mockPlayer("Hodor", 3, gameProcessPtr, nullptr, mockObserverPtr); + + gameProcessPtr->addPlayer(mockPlayerPtr1); + gameProcessPtr->addPlayer(mockPlayerPtr2); + gameProcessPtr->addPlayer(mockPlayerPtr3); + + gameProcessPtr->init(); + gameProcessPtr->reset(); + + auto mockActionPtr = std::shared_ptr(new MockAction()); + + auto actionsList = std::vector>{mockActionPtr}; + + EXPECT_CALL(*mockGridPtr, performActions(Eq(player1Id), Eq(actionsList))) + .WillOnce(Return(std::unordered_map{{1, 5}})); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(player2Id), Eq(actionsList))) + .WillOnce(Return(std::unordered_map{{3, 10}, {2, -5}})); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(player3Id), Eq(actionsList))) + .WillOnce(Return(std::unordered_map{{3, 5}})); + + EXPECT_CALL(*mockTerminationHandlerPtr, isTerminated()) + .WillRepeatedly(Return(TerminationResult{false, {}})); + + EXPECT_CALL(*mockGridPtr, update()) + .WillOnce(Return(std::unordered_map{{1, 5}})); + + auto result1 = gameProcessPtr->performActions(player1Id, actionsList); + + EXPECT_CALL(*mockGridPtr, update()) + .WillOnce(Return(std::unordered_map{})); + + auto result2 = gameProcessPtr->performActions(player2Id, actionsList); + + EXPECT_CALL(*mockGridPtr, update()) + .WillOnce(Return(std::unordered_map{})); + + auto result3 = gameProcessPtr->performActions(player3Id, actionsList); + + ASSERT_FALSE(result1.terminated); + ASSERT_FALSE(result2.terminated); + ASSERT_FALSE(result3.terminated); + + ASSERT_EQ(result1.reward, 10); + ASSERT_EQ(result2.reward, -5); + ASSERT_EQ(result3.reward, 15); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); +} + TEST(GameProcessTest, performActionsDelayedReward) { auto mockGridPtr = std::shared_ptr(new MockGrid()); std::unordered_map>> globalVariables{}; EXPECT_CALL(*mockGridPtr, getTickCount()) - .WillOnce(Return(std::make_shared(0))); + .WillRepeatedly(Return(std::make_shared(0))); EXPECT_CALL(*mockGridPtr, getGlobalVariables()) - .WillOnce(ReturnRef(globalVariables)); + .WillRepeatedly(ReturnRef(globalVariables)); + EXPECT_CALL(*mockGridPtr, getPlayerAvatarObjects()) + .WillRepeatedly(Return(std::unordered_map>{})); + EXPECT_CALL(*mockGridPtr, resetGlobalVariables(_)) + .Times(2); + auto mockLevelGeneratorPtr = std::shared_ptr(new MockLevelGenerator()); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); - auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, nullptr, mockGridPtr)); + auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + + EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) + .Times(2); + + EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + .WillRepeatedly(Return(mockLevelGeneratorPtr)); + EXPECT_CALL(*mockGDYFactory, getPlayerCount) + .WillRepeatedly(Return(1)); + EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + .WillRepeatedly(Return(PlayerObserverDefinition{})); + EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + .WillRepeatedly(Return(mockTerminationHandlerPtr)); + EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + .WillRepeatedly(Return(std::unordered_map{})); + + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); - gameProcessPtr->setTerminationHandler(mockTerminationHandlerPtr); + auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); + auto mockPlayerPtr = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); + + gameProcessPtr->addPlayer(mockPlayerPtr); + + gameProcessPtr->init(); + gameProcessPtr->reset(); auto mockActionPtr = std::shared_ptr(new MockAction()); diff --git a/tests/src/Mocks/Griddly/Core/MockGrid.hpp b/tests/src/Mocks/Griddly/Core/MockGrid.hpp index 8c063b32b..a36860e67 100644 --- a/tests/src/Mocks/Griddly/Core/MockGrid.hpp +++ b/tests/src/Mocks/Griddly/Core/MockGrid.hpp @@ -40,6 +40,7 @@ class MockGrid : public Grid { MOCK_METHOD((const TileObjects&), getObjectsAt, (glm::ivec2 location), (const)); MOCK_METHOD((std::unordered_map>), getPlayerAvatarObjects, (), (const)); + MOCK_METHOD(void, setPlayerCount, (int32_t), ()); MOCK_METHOD(std::shared_ptr, getTickCount, (), (const)); }; From 85c56ea31e23b38bc6c3997a0a729348c7cbbeae Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 20 Feb 2021 12:06:48 +0000 Subject: [PATCH 13/34] fixing bug where conditional commands were ordered randomly --- python/examples/rllib/test_ma_tag.yaml | 15 ++++---- resources/gdy-schema.json | 10 +++++ src/Griddly/Core/GDY/GDYFactory.cpp | 37 ++++++++++++------- src/Griddly/Core/GDY/GDYFactory.hpp | 8 ++-- src/Griddly/Core/GDY/Objects/Object.cpp | 17 +++++++-- src/Griddly/Core/GDY/Objects/Object.hpp | 7 ++-- .../Core/GDY/Objects/ObjectGenerator.cpp | 10 ++--- .../Core/GDY/Objects/ObjectGenerator.hpp | 4 +- .../Griddly/Core/GDY/Objects/ObjectTest.cpp | 2 +- .../Griddly/Core/GDY/Objects/MockObject.hpp | 4 +- 10 files changed, 74 insertions(+), 40 deletions(-) diff --git a/python/examples/rllib/test_ma_tag.yaml b/python/examples/rllib/test_ma_tag.yaml index 76328eaed..66271795b 100644 --- a/python/examples/rllib/test_ma_tag.yaml +++ b/python/examples/rllib/test_ma_tag.yaml @@ -4,9 +4,9 @@ Environment: Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! Observers: Block2D: - TileSize: 48 + TileSize: 128 Sprite2D: - TileSize: 48 + TileSize: 128 BackgroundTile: oryx/oryx_fantasy/floor1-1.png Vector: IncludePlayerId: true @@ -15,7 +15,7 @@ Environment: - Name: player_done InitialValue: 0 PerPlayer: true - - Name: player_done_count + - Name: tagged_count InitialValue: 0 Player: Count: 12 @@ -28,8 +28,8 @@ Environment: OffsetY: 0 AvatarObject: tagger Termination: - Win: - - eq: [ player_done_count, 11 ] + End: + - eq: [ tagged_count, 0 ] Levels: - | @@ -89,16 +89,17 @@ Actions: Commands: - set_tile: 1 - set: [ is_tagged, 1 ] + - incr: tagged_count Dst: Object: tagger - Name: tag Behaviours: - Src: + Object: tagger Preconditions: - eq: [ src.is_tagged, 1 ] - eq: [ dst.is_tagged, 0 ] - Object: tagger Commands: - reward: 2 - set_tile: 0 @@ -114,7 +115,7 @@ Actions: Arguments: [ times_tagged, 3 ] Commands: - set: [ player_done, 1 ] - - incr: player_done_count + - decr: tagged_count - reward: -5 - remove: true diff --git a/resources/gdy-schema.json b/resources/gdy-schema.json index 2979c7bf1..f7fd9b96d 100644 --- a/resources/gdy-schema.json +++ b/resources/gdy-schema.json @@ -321,6 +321,16 @@ "items": { "$ref": "#/properties/Environment/properties/Termination/definitions/terminationCondition" } + }, + "End": { + "$id": "#/properties/Environment/properties/Termination/properties/End", + "type": "array", + "title": "End Conditions", + "description": "If any of these conditions are met, the game will end.", + "additionalItems": false, + "items": { + "$ref": "#/properties/Environment/properties/Termination/definitions/terminationCondition" + } } }, "definitions": { diff --git a/src/Griddly/Core/GDY/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index 2295dfbbf..c8955806b 100644 --- a/src/Griddly/Core/GDY/GDYFactory.cpp +++ b/src/Griddly/Core/GDY/GDYFactory.cpp @@ -225,6 +225,17 @@ void GDYFactory::parsePlayerDefinition(YAML::Node playerNode) { } } +YAML::iterator GDYFactory::validateCommandPairNode(YAML::Node commandPairNodeList) const { + if (commandPairNodeList.size() > 1) { + auto line = commandPairNodeList.Mark().line; + auto errorString = fmt::format("Parse Error line {0}. Each command must be defined as a singleton list. E.g '- set: ...\n- reward: ...'. \n You may have a missing '-' before the command.", line); + spdlog::error(errorString); + throw std::invalid_argument(errorString); + } + + return commandPairNodeList.begin(); +} + void GDYFactory::parseTerminationConditions(YAML::Node terminationNode) { if (!terminationNode.IsDefined()) { return; @@ -234,7 +245,7 @@ void GDYFactory::parseTerminationConditions(YAML::Node terminationNode) { if (winNode.IsDefined()) { spdlog::debug("Parsing win conditions."); for (std::size_t c = 0; c < winNode.size(); c++) { - auto commandIt = winNode[c].begin(); + auto commandIt = validateCommandPairNode(winNode[c]); auto commandName = commandIt->first.as(); auto commandArguments = singleOrListNodeToList(commandIt->second); @@ -246,7 +257,7 @@ void GDYFactory::parseTerminationConditions(YAML::Node terminationNode) { if (loseNode.IsDefined()) { spdlog::debug("Parsing lose conditions."); for (std::size_t c = 0; c < loseNode.size(); c++) { - auto commandIt = loseNode[c].begin(); + auto commandIt = validateCommandPairNode(loseNode[c]); auto commandName = commandIt->first.as(); auto commandArguments = singleOrListNodeToList(commandIt->second); @@ -258,7 +269,7 @@ void GDYFactory::parseTerminationConditions(YAML::Node terminationNode) { if (endNode.IsDefined()) { spdlog::debug("Parsing end conditions."); for (std::size_t c = 0; c < endNode.size(); c++) { - auto commandIt = endNode[c].begin(); + auto commandIt = validateCommandPairNode(endNode[c]); auto commandName = commandIt->first.as(); auto commandArguments = singleOrListNodeToList(commandIt->second); @@ -446,8 +457,8 @@ ActionBehaviourDefinition GDYFactory::makeBehaviourDefinition(ActionBehaviourTyp std::string actionName, std::string commandName, BehaviourCommandArguments commandArguments, - std::vector> actionPreconditions, - std::unordered_map conditionalCommands) { + CommandList actionPreconditions, + CommandList conditionalCommands) { ActionBehaviourDefinition behaviourDefinition; behaviourDefinition.actionName = actionName; behaviourDefinition.behaviourType = behaviourType; @@ -482,17 +493,17 @@ void GDYFactory::parseActionBehaviours(ActionBehaviourType actionBehaviourType, } // Get preconditions - std::vector> actionPreconditions; + CommandList actionPreconditions; if (preconditionsNode.IsDefined()) { for (std::size_t c = 0; c < preconditionsNode.size(); c++) { - auto preconditionsIt = preconditionsNode[c].begin(); + auto preconditionsIt = validateCommandPairNode(preconditionsNode[c]); auto preconditionCommandName = preconditionsIt->first.as(); auto preconditionCommandArgumentsNode = preconditionsIt->second; auto preconditionCommandArgumentMap = singleOrListNodeToCommandArguments(preconditionCommandArgumentsNode); - actionPreconditions.push_back({{preconditionCommandName, preconditionCommandArgumentMap}}); + actionPreconditions.push_back(std::make_pair(preconditionCommandName, preconditionCommandArgumentMap)); } } @@ -506,7 +517,7 @@ void GDYFactory::parseActionBehaviours(ActionBehaviourType actionBehaviourType, } for (std::size_t c = 0; c < commandsNode.size(); c++) { - auto commandIt = commandsNode[c].begin(); + auto commandIt = validateCommandPairNode(commandsNode[c]); // iterate through keys auto commandName = commandIt->first.as(); auto commandNode = commandIt->second; @@ -524,7 +535,7 @@ void GDYFactory::parseCommandNode( std::string objectName, std::string actionName, std::vector associatedObjectNames, - std::vector> actionPreconditions) { + CommandList actionPreconditions) { if (commandNode.IsMap()) { // TODO: don't really like this check being done here. should be pushed into the object class really? if (commandName == "exec") { @@ -552,9 +563,9 @@ void GDYFactory::parseCommandNode( auto commandArgumentMap = singleOrListNodeToCommandArguments(conditionArguments); - std::unordered_map parsedSubCommands; + CommandList parsedSubCommands; for (std::size_t sc = 0; sc < conditionSubCommands.size(); sc++) { - auto subCommandIt = conditionSubCommands[sc].begin(); + auto subCommandIt = validateCommandPairNode(conditionSubCommands[sc]); auto subCommandName = subCommandIt->first.as(); auto subCommandArguments = subCommandIt->second; @@ -562,7 +573,7 @@ void GDYFactory::parseCommandNode( spdlog::debug("Parsing subcommand {0} conditions", subCommandName); - parsedSubCommands.insert({subCommandName, subCommandArgumentMap}); + parsedSubCommands.push_back(std::make_pair(subCommandName, subCommandArgumentMap)); } for (auto associatedObjectName : associatedObjectNames) { diff --git a/src/Griddly/Core/GDY/GDYFactory.hpp b/src/Griddly/Core/GDY/GDYFactory.hpp index a1b08e3b6..bf0569e01 100644 --- a/src/Griddly/Core/GDY/GDYFactory.hpp +++ b/src/Griddly/Core/GDY/GDYFactory.hpp @@ -27,8 +27,8 @@ class GDYFactory { std::string actionName, std::string commandName, BehaviourCommandArguments commandArguments, - std::vector> actionPreconditions, - std::unordered_map conditionalCommands); + CommandList actionPreconditions, + CommandList conditionalCommands); void initializeFromFile(std::string filename); @@ -69,6 +69,8 @@ class GDYFactory { virtual PlayerObserverDefinition getPlayerObserverDefinition() const; virtual std::string getAvatarObject() const; + virtual YAML::iterator validateCommandPairNode(YAML::Node commandPairNodeList) const; + private: void parseActionBehaviours( ActionBehaviourType actionBehaviourType, @@ -106,7 +108,7 @@ class GDYFactory { std::string objectName, std::string actionName, std::vector associatedObjectNames, - std::vector> actionPreconditions); + CommandList actionPreconditions); std::unordered_map defaultActionInputMappings() const; void loadActionInputsDefinition(std::string actionName, YAML::Node actionInputMappingNode); diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index 83c8e80ce..ceaa5b69c 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -142,7 +142,7 @@ PreconditionFunction Object::instantiatePrecondition(std::string commandName, Be }; } -BehaviourFunction Object::instantiateConditionalBehaviour(std::string commandName, BehaviourCommandArguments commandArguments, std::unordered_map subCommands) { +BehaviourFunction Object::instantiateConditionalBehaviour(std::string commandName, BehaviourCommandArguments commandArguments, CommandList subCommands) { if (subCommands.size() == 0) { return instantiateBehaviour(commandName, commandArguments); } @@ -205,6 +205,8 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto value = commandArguments["0"].as(0); return [this, value](std::shared_ptr action) -> BehaviourResult { // Find the player id of this object and give rewards to this player. + + spdlog::debug("reward"); return {false, {{*playerId_, value}}}; }; } @@ -247,7 +249,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto a = variablePointers["0"]; auto b = variablePointers["1"]; return [this, a, b](std::shared_ptr action) -> BehaviourResult { + spdlog::debug("set"); *a->resolve_ptr(action) = b->resolve(action); + return {}; }; } @@ -256,7 +260,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; return [this, a](std::shared_ptr action) -> BehaviourResult { + spdlog::debug("incr"); (*a->resolve_ptr(action)) += 1; + return {}; }; } @@ -265,7 +271,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto variablePointers = resolveVariables(commandArguments); auto a = variablePointers["0"]; return [this, a](std::shared_ptr action) -> BehaviourResult { + spdlog::debug("decr"); (*a->resolve_ptr(action)) -= 1; + return {}; }; } @@ -369,6 +377,7 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou if (commandName == "remove") { return [this](std::shared_ptr action) -> BehaviourResult { + spdlog::debug("remove"); removeObject(); return {}; }; @@ -377,7 +386,9 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou if (commandName == "set_tile") { auto tileId = commandArguments["0"].as(); return [this, tileId](std::shared_ptr action) -> BehaviourResult { + spdlog::debug("set_tile"); setRenderTileId(tileId); + grid_->invalidateLocation({*x_, *y_}); return {}; }; } @@ -408,7 +419,7 @@ void Object::addActionSrcBehaviour( std::string destinationObjectName, std::string commandName, BehaviourCommandArguments commandArguments, - std::unordered_map conditionalCommands) { + CommandList conditionalCommands) { spdlog::debug("Adding behaviour command={0} when action={1} is performed on object={2} by object={3}", commandName, actionName, destinationObjectName, getObjectName()); // This object can perform this action @@ -423,7 +434,7 @@ void Object::addActionDstBehaviour( std::string sourceObjectName, std::string commandName, BehaviourCommandArguments commandArguments, - std::unordered_map conditionalCommands) { + CommandList conditionalCommands) { spdlog::debug("Adding behaviour command={0} when object={1} performs action={2} on object={3}", commandName, sourceObjectName, actionName, getObjectName()); auto behaviourFunction = instantiateConditionalBehaviour(commandName, commandArguments, conditionalCommands); diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index 94870d2ff..d0c3ca2f2 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -16,6 +16,7 @@ #define BehaviourCommandArguments std::unordered_map #define BehaviourFunction std::function)> #define PreconditionFunction std::function)> +#define CommandList std::vector> namespace griddly { @@ -82,9 +83,9 @@ class Object : public std::enable_shared_from_this { virtual BehaviourResult onActionDst(std::shared_ptr action); - virtual void addActionSrcBehaviour(std::string action, std::string destinationObjectName, std::string commandName, BehaviourCommandArguments commandArguments, std::unordered_map nestedCommands); + virtual void addActionSrcBehaviour(std::string action, std::string destinationObjectName, std::string commandName, BehaviourCommandArguments commandArguments, CommandList nestedCommands); - virtual void addActionDstBehaviour(std::string action, std::string sourceObjectName, std::string commandName, BehaviourCommandArguments commandArguments, std::unordered_map nestedCommands); + virtual void addActionDstBehaviour(std::string action, std::string sourceObjectName, std::string commandName, BehaviourCommandArguments commandArguments, CommandList nestedCommands); virtual std::shared_ptr getVariableValue(std::string variableName); @@ -145,7 +146,7 @@ class Object : public std::enable_shared_from_this { PreconditionFunction instantiatePrecondition(std::string commandName, BehaviourCommandArguments commandArguments); BehaviourFunction instantiateBehaviour(std::string commandName, BehaviourCommandArguments commandArguments); - BehaviourFunction instantiateConditionalBehaviour(std::string commandName, BehaviourCommandArguments commandArguments, std::unordered_map subCommands); + BehaviourFunction instantiateConditionalBehaviour(std::string commandName, BehaviourCommandArguments commandArguments, CommandList subCommands); std::string getStringMapValue(std::unordered_map map, std::string mapKey); }; diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp index 7053f3f64..4be0f0adf 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp @@ -90,12 +90,11 @@ std::shared_ptr ObjectGenerator::cloneInstance(std::shared_ptr t // Adding the acion preconditions for (auto actionPrecondition : actionBehaviourDefinition.actionPreconditions) { - auto precondition = actionPrecondition.begin(); initializedObject->addPrecondition( actionBehaviourDefinition.actionName, actionBehaviourDefinition.destinationObjectName, - precondition->first, - precondition->second); + actionPrecondition.first, + actionPrecondition.second); } initializedObject->addActionSrcBehaviour( @@ -171,12 +170,11 @@ std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uin // Adding the acion preconditions for (auto actionPrecondition : actionBehaviourDefinition.actionPreconditions) { - auto precondition = actionPrecondition.begin(); initializedObject->addPrecondition( actionBehaviourDefinition.actionName, actionBehaviourDefinition.destinationObjectName, - precondition->first, - precondition->second); + actionPrecondition.first, + actionPrecondition.second); } initializedObject->addActionSrcBehaviour( diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp index 2f6c8e13a..da3bde46c 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.hpp @@ -20,8 +20,8 @@ struct ActionBehaviourDefinition { std::string actionName; std::string commandName; BehaviourCommandArguments commandArguments; - std::vector> actionPreconditions; - std::unordered_map conditionalCommands; + CommandList actionPreconditions; + CommandList conditionalCommands; }; struct ObjectDefinition { diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index 1af6ba890..61667a6e8 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -305,7 +305,7 @@ std::shared_ptr setupObject(std::string objectname, std::unordered_map action, std::string commandName, BehaviourCommandArguments commandArgumentMap, std::unordered_map conditionalCommands, std::shared_ptr srcObjectPtr, std::shared_ptr dstObjectPtr) { +BehaviourResult addCommandsAndExecute(ActionBehaviourType type, std::shared_ptr action, std::string commandName, BehaviourCommandArguments commandArgumentMap, CommandList conditionalCommands, std::shared_ptr srcObjectPtr, std::shared_ptr dstObjectPtr) { switch (type) { case ActionBehaviourType::DESTINATION: { dstObjectPtr->addActionDstBehaviour(action->getActionName(), srcObjectPtr->getObjectName(), commandName, commandArgumentMap, conditionalCommands); diff --git a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp index 4140c7671..dc87d07a3 100644 --- a/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp +++ b/tests/src/Mocks/Griddly/Core/GDY/Objects/MockObject.hpp @@ -33,7 +33,7 @@ class MockObject : public Object { MOCK_METHOD(std::unordered_set, getAvailableActionNames, (), (const)); MOCK_METHOD((std::unordered_map>), getAvailableVariables, (), (const)); - MOCK_METHOD(void, addActionSrcBehaviour, (std::string action, std::string destinationObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (std::unordered_map conditionalCommands)), (override)); - MOCK_METHOD(void, addActionDstBehaviour, (std::string action, std::string sourceObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (std::unordered_map conditionalCommands)), (override)); + MOCK_METHOD(void, addActionSrcBehaviour, (std::string action, std::string destinationObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (CommandList conditionalCommands)), (override)); + MOCK_METHOD(void, addActionDstBehaviour, (std::string action, std::string sourceObjectName, std::string commandName, (BehaviourCommandArguments commandArguments), (CommandList conditionalCommands)), (override)); }; } // namespace griddly \ No newline at end of file From 04f1b69dc17e12810f7c47ef0693ec390df18c8d Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 20 Feb 2021 20:21:31 +0000 Subject: [PATCH 14/34] working on more rllib example stuff --- python/examples/rllib/rllib_RTS.py | 15 +++++---------- python/examples/rllib/rllib_multiagent_taggers.py | 8 ++++---- python/setup.py | 4 ++-- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index be34624a7..b2be0fdc7 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -11,30 +11,25 @@ from griddly.util.rllib.torch import GAPAgent from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer + if __name__ == '__main__': sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1, local_mode=True) + ray.init(num_gpus=1) env_name = 'ray-griddly-rts-env' register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) - #test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_rts.yaml') - config = { 'framework': 'torch', - 'num_workers': 8, - 'num_envs_per_worker': 4, - - 'rollout_fragment_length': 10, - 'train_batch_size': 200, + 'num_workers': 11, + 'num_envs_per_worker': 1, # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, - 'monitor': True, 'model': { 'custom_model': 'GAP', 'custom_model_config': {} @@ -56,7 +51,7 @@ } stop = { - 'timesteps_total': 2000000, + 'timesteps_total': 20000000, } result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index 1e9c6ef2c..6b7f19966 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -30,8 +30,7 @@ config = { 'framework': 'torch', - - 'num_workers': 8, + 'num_workers': 6, 'num_envs_per_worker': 1, 'model': { 'custom_model': 'GAP', @@ -50,8 +49,9 @@ 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 0, - 'max_steps': 1000, - } + 'max_steps': 500, + }, + 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } stop = { diff --git a/python/setup.py b/python/setup.py index c42d1768c..ace748ac9 100644 --- a/python/setup.py +++ b/python/setup.py @@ -4,7 +4,7 @@ from sys import platform import shutil -from setuptools import setup, Distribution +from setuptools import setup, Distribution, find_packages from setuptools.command.install import install from setuptools.command.develop import develop @@ -79,7 +79,7 @@ def griddly_package_data(config='Debug'): url="https://github.com/bam4d/Griddly", package_data={'griddly': griddly_package_data('Release')}, - packages=['griddly', 'griddly.util', 'griddly.util.wrappers', 'griddly.util.rllib', 'griddly.util.rllib.torch'], + packages=find_packages(include='griddly*'), install_requires=[ "numpy>=1.19.1", "gym>=0.17.2", From 8105ba029b5ddc64a7dcd42b153a591ec5812015 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 22 Feb 2021 08:39:13 +0000 Subject: [PATCH 15/34] fixing avatar removal bug when players have no avatar --- python/examples/rllib/GriddlyRTS_test.yaml | 361 ++++++++++++++++++ python/examples/rllib/rllib_RTS.py | 15 +- .../rllib/rllib_multiagent_taggers.py | 5 +- python/examples/snippet.py | 18 +- python/setup.py | 1 - .../Core/GDY/Objects/ObjectGenerator.cpp | 3 +- src/Griddly/Core/GameProcess.cpp | 7 + src/Griddly/Core/Grid.cpp | 2 +- src/Griddly/Core/Observers/VectorObserver.cpp | 4 +- 9 files changed, 395 insertions(+), 21 deletions(-) create mode 100644 python/examples/rllib/GriddlyRTS_test.yaml diff --git a/python/examples/rllib/GriddlyRTS_test.yaml b/python/examples/rllib/GriddlyRTS_test.yaml new file mode 100644 index 000000000..fe73f813d --- /dev/null +++ b/python/examples/rllib/GriddlyRTS_test.yaml @@ -0,0 +1,361 @@ +Version: "0.1" +Environment: + Name: GriddlyRTS + Description: An RTS Game. There's aliens and stuff. + Observers: + Sprite2D: + TileSize: 16 + BackgroundTile: oryx/oryx_tiny_galaxy/tg_sliced/tg_world/tg_world_floor_panel_metal_a.png + Isometric: + TileSize: [ 32, 48 ] + BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png + IsoTileHeight: 16 + IsoTileDepth: 4 + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_resources + InitialValue: 0 + PerPlayer: true + Player: + Count: 2 + Termination: + Lose: + - eq: [ base:count, 0 ] # If the player has no bases + Levels: + - | + W W W W W W W W W W W W + W B2 . H2 . . . . . . . W + W . H2 . . . . . . . . W + W H2 . . . M M . . . . W + W . . . M M M M . . . W + W . . . . M M . . . H1 W + W . . . . . . . . H1 . W + W . . . . . . . H1 . B1 W + W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . B1 . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . H1 . . . . . . . . . . . . . . . . . . . . . . W + W . . . H1 . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . M M M M M . . . . . . . . . . . . . W + W . . . . . . . . . . M M M M M M . . . . . . . . . . . . W + W . . . . . . . . . M M . M M M M . . . . . . . . . . . . W + W . . . . . . . . . M . M M M . M . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . M M M M . . . . . . . . . . W + W . . . . . . . . . . . . . . M M M M M M . . . . . . . . W + W . . . . . . . . . . . . . M . . M . M M . . . . . . . . W + W . . . . . . . . . . . . . M M M M M M M . . . . . . . . W + W . . . . . . . . . . . . . . . M M M M . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . H2 . . . . . . W + W . . . . . . . . . . . . . . . . . . H2 . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . B2 . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . B1 . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . H1 . . . . . . . . . . . . . . . . . . . . . . W + W . . . H1 . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . M M M M M . . . . . . . . . . . . . W + W . . . . . . . . . . M M M M M M . . . . . . . . . . . . W + W . . . . . . . . . M M . M M M M . . . . . . . . . . . . W + W . . . . . . . . . M . M M M . M . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W + W W W W W W W W W . . . . . . . . . . . . . . W W W W W w W + W . . . . . . W W . . . w w w w w w . . . . . W W W W W w W + W . . . . . . . . . . . . . w w . . . . . . . W W W W W w W + W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W + W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W + W . . . . . . . . . . . . . . M M M M . . . . W W W W W w W + W . . . . P1 w . . . . . . . . M M M M M M . W W W W W W W W + W . . . . P1 w . . . . . . . M . . M . M M . . . . . . . . W + W . . . . P2 w . . . . . . . M M M M M M M . . . . . . . . W + W . . . . P2 w . . . . . . . . . M M M M . . . . . . . . . W + W . . . . P1 w . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . H2 . . . . . . W + W . . . . . . . . . . . . . . . . . . H2 . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . B2 . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . M M M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . M M M M W + W . . . . . M M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . M M W + W . . . . . . M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . H2 . . M W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . B2 H2 . M W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W W W w w W W W W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W W . . W W W W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W W W W W W W W W W W W W w w w w w w w w w w w w w W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W M . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W M M . . . . . . . . . . . . W . . . . . . . . . . . . M . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W M M M . . . . . . . . . . . W . . . . . . . . . . . M M M . . . . . . . . . . . W . . . . . . . . . . . . . . W + W W W W W W W W W W W . . . . W . . . . . . . . . . M M M M M . . . . . . . . . . W . . . . W W W W W W W W W W W + W . . . . . . . . . . . . . . W . . . . . . . . . . M M M M M . . . . . . . . . . W . . . . . . . . . . . M M M W + W . . . . . . . . . . . . . . W . . . . . . . . . . . M M M . . . . . . . . . . . W . . . . . . . . . . . . M M W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . M . . . . . . . . . . . . W . . . . . . . . . . . . . M W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . W w w w w w w w w w w w w w W W W W W W W W W W W W W . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W + W W W W . . W W W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W W w w W W W W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W M . H1 B1 . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W + W M . . H1 . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M . . . . . . W + W M M . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M M . . . . . W + W M M M M . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M M M . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + +Actions: + # - Name: spawn_harvester + # InputMapping: + # Internal: true + # Behaviours: + # - Src: + # Object: base + # Commands: + # - spawn: harvester + # Dst: + # Object: _empty + # + # + # - Src: + # Object: base + # Dst: + # Object: [base, puncher, harvester, pusher, movable_wall] + # Commands: + # - exec: + # Action: spawn_harvester + # Delay: 1 + # Randomize: true + # + # # Harvester costs 5 resources to build + # - Name: build_harvester + # Behaviours: + # - Src: + # Preconditions: + # - gt: [player_resources, 5] + # Object: base + # Dst: + # Object: base + # Commands: + # - exec: + # Action: spawn_harvester + # Delay: 10 + # Randomize: true + + + - Name: gather + Behaviours: + - Src: + Object: harvester + Preconditions: + - lt: [ resources, 10 ] + Commands: + - incr: resources + - reward: 1 + Dst: + Object: minerals + Commands: + - decr: resources + - eq: + Arguments: [ resources, 0 ] + Commands: + - remove: true + - Src: + Object: harvester + Preconditions: + - gt: [ resources, 1 ] + - eq: [ src._playerId, dst._playerId ] + Commands: + - decr: resources + - reward: 1 + Dst: + Object: base + Commands: + - incr: player_resources + + - Name: move + Behaviours: + - Src: + Object: [ harvester, puncher, pusher, movable_wall ] + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + +# - Src: +# Object: pusher +# Commands: +# - mov: _dest # mov will move the object, _dest is the destination location of the action +# Dst: +# Object: [movable_wall, harvester, puncher] +# Commands: +# - cascade: _dest # reapply the same action to the dest location of the action + +# - Name: punch +# Behaviours: +# - Src: +# Object: puncher +# Commands: +# - reward: 1 +# Dst: +# Object: [puncher, harvester, pusher, base] +# Commands: +# - decr: health +# - eq: +# Arguments: [0, health] +# Commands: +# - remove: true + +Objects: + - Name: minerals + MapCharacter: M + Variables: + - Name: resources + InitialValue: 1 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_crystal_green.png + Block2D: + - Shape: triangle + Color: [ 0.0, 1.0, 0.0 ] + Scale: 1.0 + Isometric: + - Image: oryx/oryx_iso_dungeon/minerals-1.png + + - Name: harvester + MapCharacter: H + Variables: + - Name: resources + InitialValue: 0 + - Name: health + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_jelly_d1.png + Block2D: + - Shape: square + Color: [ 0.6, 0.2, 0.2 ] + Scale: 0.5 + Isometric: + - Image: oryx/oryx_iso_dungeon/jelly-1.png + + - Name: pusher + MapCharacter: P + Variables: + - Name: health + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_crawler_queen_d1.png + Block2D: + - Shape: square + Color: [ 0.2, 0.2, 0.6 ] + Scale: 1.0 + Isometric: + - Image: oryx/oryx_iso_dungeon/queen-1.png + + - Name: puncher + MapCharacter: p + Variables: + - Name: health + InitialValue: 5 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_beast_d1.png + Block2D: + - Color: [ 0.2, 0.6, 0.6 ] + Shape: square + Scale: 0.8 + Isometric: + - Image: oryx/oryx_iso_dungeon/beast-1.png + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_2 # Will tile walls with two images + Image: + - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img33.png + - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img40.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square + Isometric: + - Image: oryx/oryx_iso_dungeon/wall-grey-1.png + + - Name: movable_wall + MapCharacter: w + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img282.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + Isometric: + - Image: oryx/oryx_iso_dungeon/crate-1.png + + - Name: base + MapCharacter: B + Variables: + - Name: health + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img324.png + Block2D: + - Color: [ 0.8, 0.8, 0.3 ] + Shape: triangle + Isometric: + - Image: oryx/oryx_iso_dungeon/base-1.png diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS.py index b2be0fdc7..11a570a08 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS.py @@ -20,13 +20,15 @@ env_name = 'ray-griddly-rts-env' + test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') + register_env(env_name, RLlibMultiAgentWrapper) ModelCatalog.register_custom_model('GAP', GAPAgent) config = { 'framework': 'torch', - 'num_workers': 11, - 'num_envs_per_worker': 1, + 'num_workers': 8, + 'num_envs_per_worker': 4, # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, @@ -40,18 +42,19 @@ 'invalid_action_masking': True, 'record_video_config': { - 'frequency': 10000 # number of rollouts + 'frequency': 100000 # number of rollouts }, - 'yaml_file': 'RTS/GriddlyRTS.yaml', + 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.ISOMETRIC, - 'level': 0, + 'level': 1, 'max_steps': 1000, }, + 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } stop = { - 'timesteps_total': 20000000, + 'timesteps_total': 200000000, } result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index 6b7f19966..38cff27cd 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -1,12 +1,9 @@ import os import sys -import numpy as np import ray -from gym.spaces import Box, MultiDiscrete from ray import tune from ray.rllib.agents.impala import ImpalaTrainer -from ray.rllib.agents.ppo import PPOTrainer from ray.rllib.models import ModelCatalog from ray.tune.registry import register_env @@ -55,7 +52,7 @@ } stop = { - 'timesteps_total': 10000000, + 'timesteps_total': 50000000, } result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/snippet.py b/python/examples/snippet.py index 987b1cecc..890063382 100644 --- a/python/examples/snippet.py +++ b/python/examples/snippet.py @@ -3,19 +3,22 @@ import gym from griddly import GymWrapperFactory, gd +from griddly.util.wrappers import ValidActionSpaceWrapper + def make_env(name): wrapper = GymWrapperFactory() - wrapper.build_gym_from_yaml(name, 'rllib/test_ma_tag.yaml', + wrapper.build_gym_from_yaml(name, 'rllib/GriddlyRTS_test.yaml', player_observer_type=gd.ObserverType.SPRITE_2D, - global_observer_type=gd.ObserverType.SPRITE_2D, - level=0) + global_observer_type=gd.ObserverType.VECTOR, + level=0, + max_steps=200) env = gym.make(f'GDY-{name}-v0') env.enable_history(True) env.reset() - return env + return ValidActionSpaceWrapper(env) if __name__ == '__main__': @@ -29,13 +32,13 @@ def make_env(name): fps_samples = [] - for s in range(1000): + for s in range(100000): action = env.action_space.sample() frames += 1 obs, reward, done, info = env.step(action) - env.render() + #env.render() env.render(observer='global') if frames % 1000 == 0: @@ -45,4 +48,7 @@ def make_env(name): print(f'fps: {fps}') frames = 0 start = timer() + + if done: + env.reset() print(f'mean fps: {np.mean(fps_samples)}') \ No newline at end of file diff --git a/python/setup.py b/python/setup.py index ace748ac9..9dbe0bd96 100644 --- a/python/setup.py +++ b/python/setup.py @@ -77,7 +77,6 @@ def griddly_package_data(config='Debug'): long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/bam4d/Griddly", - package_data={'griddly': griddly_package_data('Release')}, packages=find_packages(include='griddly*'), install_requires=[ diff --git a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp index 4be0f0adf..cc7c3fc7f 100644 --- a/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp +++ b/src/Griddly/Core/GDY/Objects/ObjectGenerator.cpp @@ -146,13 +146,12 @@ std::shared_ptr ObjectGenerator::newInstance(std::string objectName, uin auto variableName = globalVariable.first; auto globalVariableInstances = globalVariable.second; - if (globalVariableInstances.size() == 1) { spdlog::debug("Adding reference to global variable {0} to object {1}", variableName, objectName); + if (globalVariableInstances.size() == 1) { auto instance = globalVariableInstances.at(0); availableVariables.insert({variableName, instance}); } else { auto instance = globalVariableInstances.at(playerId); - spdlog::debug("Adding reference to player variable {0} with value {1} to object {2}", variableName, *instance, objectName); availableVariables.insert({variableName, instance}); } } diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index 3e95a8cca..1aa15ba39 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -129,6 +129,7 @@ uint8_t* GameProcess::resetObservers() { for (auto& p : players_) { p->reset(); + spdlog::debug("{0} player avatar objects to reset", playerAvatarObjects.size()); if (playerAvatarObjects.size() > 0) { p->setAvatar(playerAvatarObjects.at(p->getId())); } @@ -146,14 +147,20 @@ uint8_t* GameProcess::reset() { throw std::runtime_error("Cannot reset game process before initialization."); } + + spdlog::debug("Resetting player count."); grid_->setPlayerCount(gdyFactory_->getPlayerCount()); + spdlog::debug("Resetting global variables."); grid_->resetGlobalVariables(gdyFactory_->getGlobalVariableDefinitions()); + spdlog::debug("Resetting level generator."); levelGenerator_->reset(grid_); + spdlog::debug("Resetting Observers."); auto observation = resetObservers(); + spdlog::debug("Resetting Termination Handler."); terminationHandler_ = std::shared_ptr(gdyFactory_->createTerminationHandler(grid_, players_)); requiresReset_ = false; diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 8ec9f12e1..0ce3215fd 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -430,7 +430,7 @@ bool Grid::removeObject(std::shared_ptr object) { invalidateLocation(location); // if we are removing a player's avatar - if(playerAvatars_[playerId] == object) { + if(playerAvatars_.size() > 0 && playerAvatars_.at(playerId) == object) { spdlog::debug("Removing player {0} avatar {1}", playerId, objectName); playerAvatars_.erase(playerId); } diff --git a/src/Griddly/Core/Observers/VectorObserver.cpp b/src/Griddly/Core/Observers/VectorObserver.cpp index ede057b82..16f417391 100644 --- a/src/Griddly/Core/Observers/VectorObserver.cpp +++ b/src/Griddly/Core/Observers/VectorObserver.cpp @@ -77,7 +77,9 @@ void VectorObserver::renderLocation(glm::ivec2 objectLocation, glm::ivec2 output bool processTopLayer = true; for (auto& objectIt : grid_->getObjectsAt(objectLocation)) { auto object = objectIt.second; - auto memPtrObject = memPtr + grid_->getObjectIds().at(object->getObjectName()); + auto objectName = object->getObjectName(); + spdlog::debug("Rendering object {0}", objectName); + auto memPtrObject = memPtr + grid_->getObjectIds().at(objectName); *memPtrObject = 1; if (processTopLayer) { From 512ca28b4b0486cd81b2cabb08ea5e207d533020 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Fri, 26 Feb 2021 16:39:29 +0000 Subject: [PATCH 16/34] more work on rllib support --- python/examples/rllib/README.md | 2 +- python/examples/rllib/rllib_RTS_Gridnet.py | 59 +++++++++++++++++++ .../rllib/{rllib_RTS.py => rllib_RTS_UAS.py} | 6 +- .../rllib/rllib_multiagent_taggers.py | 7 ++- python/examples/rllib/rllib_single_agent.py | 6 +- python/examples/rllib/test_ma_tag.yaml | 4 +- python/griddly/util/rllib/torch/__init__.py | 2 +- .../util/rllib/torch/agents/__init__.py | 0 .../global_average_pooling_agent.py | 4 +- .../util/rllib/torch/agents/gridnet_agent.py | 54 +++++++++++++++++ .../torch/impala/im_vtrace_torch_policy.py | 2 - python/griddly/util/rllib/wrappers.py | 2 + .../GVGAI/random_butterflies.yaml | 2 +- 13 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 python/examples/rllib/rllib_RTS_Gridnet.py rename python/examples/rllib/{rllib_RTS.py => rllib_RTS_UAS.py} (94%) create mode 100644 python/griddly/util/rllib/torch/agents/__init__.py rename python/griddly/util/rllib/torch/{ => agents}/global_average_pooling_agent.py (96%) create mode 100644 python/griddly/util/rllib/torch/agents/gridnet_agent.py diff --git a/python/examples/rllib/README.md b/python/examples/rllib/README.md index 7bb592031..bd8333906 100644 --- a/python/examples/rllib/README.md +++ b/python/examples/rllib/README.md @@ -1,4 +1,4 @@ -# Griddly Rlib Examples +# EXERIMENTAL Griddly Rlib Examples ## Installation diff --git a/python/examples/rllib/rllib_RTS_Gridnet.py b/python/examples/rllib/rllib_RTS_Gridnet.py new file mode 100644 index 000000000..98522fce9 --- /dev/null +++ b/python/examples/rllib/rllib_RTS_Gridnet.py @@ -0,0 +1,59 @@ +import os +import sys + +import ray +from ray import tune +from ray.rllib.models import ModelCatalog +from ray.tune.registry import register_env + +from griddly import gd +from griddly.util.rllib import RLlibMultiAgentWrapper +from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer + + +class GridnetAgent(object): + pass + + +if __name__ == '__main__': + sep = os.pathsep + os.environ['PYTHONPATH'] = sep.join(sys.path) + + ray.init(num_gpus=1) + + env_name = 'ray-griddly-rts-env' + + test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') + + register_env(env_name, RLlibMultiAgentWrapper) + ModelCatalog.register_custom_model('GridnetAgent', GridnetAgent) + + config = { + 'framework': 'torch', + 'num_workers': 8, + 'num_envs_per_worker': 1, + + 'model': { + 'custom_model': 'GridnetAgent', + 'custom_model_config': {} + }, + 'env': env_name, + 'env_config': { + 'record_video_config': { + 'frequency': 100000 # number of rollouts + }, + + 'yaml_file': test_path, + 'global_observer_type': gd.ObserverType.ISOMETRIC, + 'level': 1, + 'max_steps': 1000, + }, + 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) + } + + stop = { + 'timesteps_total': 200000000, + } + + result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_RTS.py b/python/examples/rllib/rllib_RTS_UAS.py similarity index 94% rename from python/examples/rllib/rllib_RTS.py rename to python/examples/rllib/rllib_RTS_UAS.py index 11a570a08..1c1e98381 100644 --- a/python/examples/rllib/rllib_RTS.py +++ b/python/examples/rllib/rllib_RTS_UAS.py @@ -16,7 +16,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + ray.init(num_gpus=1, local_mode=True) env_name = 'ray-griddly-rts-env' @@ -27,8 +27,8 @@ config = { 'framework': 'torch', - 'num_workers': 8, - 'num_envs_per_worker': 4, + 'num_workers': 1, + 'num_envs_per_worker': 1, # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index 38cff27cd..719e3f75c 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -29,6 +29,9 @@ 'framework': 'torch', 'num_workers': 6, 'num_envs_per_worker': 1, + + 'train_batch_size': 2048, + 'model': { 'custom_model': 'GAP', 'custom_model_config': {} @@ -45,14 +48,14 @@ 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, - 'level': 0, + 'level': 1, 'max_steps': 500, }, 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } stop = { - 'timesteps_total': 50000000, + 'timesteps_total': 2000000, } result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 11c073924..c1890cf28 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -38,9 +38,9 @@ 'frequency': 10000 }, - 'yaml_file': 'Single-Player/GVGAI/random_butterflies.yaml', + 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, - 'level': 6, + 'level': 3, 'max_steps': 1000, }, 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) @@ -48,7 +48,7 @@ stop = { # "training_iteration": 100, - "timesteps_total": 2000000, + "timesteps_total": 5000000, } result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/test_ma_tag.yaml b/python/examples/rllib/test_ma_tag.yaml index 66271795b..cf93e5132 100644 --- a/python/examples/rllib/test_ma_tag.yaml +++ b/python/examples/rllib/test_ma_tag.yaml @@ -4,9 +4,9 @@ Environment: Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! Observers: Block2D: - TileSize: 128 + TileSize: 24 Sprite2D: - TileSize: 128 + TileSize: 24 BackgroundTile: oryx/oryx_fantasy/floor1-1.png Vector: IncludePlayerId: true diff --git a/python/griddly/util/rllib/torch/__init__.py b/python/griddly/util/rllib/torch/__init__.py index 5aa3c5d5b..aeeda35cb 100644 --- a/python/griddly/util/rllib/torch/__init__.py +++ b/python/griddly/util/rllib/torch/__init__.py @@ -1 +1 @@ -from griddly.util.rllib.torch.global_average_pooling_agent import GAPAgent \ No newline at end of file +from griddly.util.rllib.torch.agents.global_average_pooling_agent import GAPAgent \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/agents/__init__.py b/python/griddly/util/rllib/torch/agents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/torch/global_average_pooling_agent.py b/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py similarity index 96% rename from python/griddly/util/rllib/torch/global_average_pooling_agent.py rename to python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py index a8ad58dd6..7fcbf67cc 100644 --- a/python/griddly/util/rllib/torch/global_average_pooling_agent.py +++ b/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py @@ -4,8 +4,8 @@ def layer_init(layer, std=np.sqrt(2), bias_const=0.0): - # nn.init.orthogonal_(layer.weight, std) - # nn.init.constant_(layer.bias, bias_const) + nn.init.orthogonal_(layer.weight, std) + nn.init.constant_(layer.bias, bias_const) return layer diff --git a/python/griddly/util/rllib/torch/agents/gridnet_agent.py b/python/griddly/util/rllib/torch/agents/gridnet_agent.py new file mode 100644 index 000000000..31e753f01 --- /dev/null +++ b/python/griddly/util/rllib/torch/agents/gridnet_agent.py @@ -0,0 +1,54 @@ +from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 +from torch import nn +import torch + +from griddly.util.rllib.torch.agents.global_average_pooling_agent import layer_init + + +class GridnetAgent(TorchModelV2, nn.Module): + def __init__(self, obs_space, action_space, num_outputs, model_config, name): + super().__init__(obs_space, action_space, num_outputs, model_config, name) + nn.Module.__init__(self) + + height = obs_space.original_space['obs'].shape[0] + width = obs_space.original_space['obs'].shape[1] + + grid_channels = 78 + + self.encoder = nn.Sequential( + layer_init(nn.Conv2d(27, 32, kernel_size=5, padding=2)), + nn.ReLU(), + layer_init(nn.Conv2d(32, 64, kernel_size=5, padding=2)), + nn.ReLU(), + layer_init(nn.Conv2d(128, 128, kernel_size=3, padding=1)), + nn.ReLU() + ) + + self.actor = nn.Sequential( + layer_init(nn.Conv2d(128, grid_channels, kernel_size=1)), + ) + + self.critic = nn.Sequential( + nn.Flatten(), + layer_init(nn.Linear(64 * height * width, 256)), + nn.ReLU(), + layer_init(nn.Linear(256, 1), std=1)) + + def forward(self, input_dict, state, seq_lens): + input_obs = input_dict['obs']['obs'] + input_mask = input_dict['obs']['invalid_action_masks'] + self._encoded = self.encoder(input_obs) + + # Value function + value = self.critic(self._encoded) + self._value = value.reshape(-1) + + # Logits for actions + logits = self.actor(self._encoded) + + masked_logits = logits + torch.log(input_mask) + + return masked_logits, state + + def value_function(self): + return self._value \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py index 43c71f704..f7948a392 100644 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py @@ -41,8 +41,6 @@ def _make_time_major(*args, **kw): behaviour_action_logp = train_batch[SampleBatch.ACTION_LOGP] behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS] - # apply mask so target distribution is masked in the same way as the workers - # However instead of masking to very small negative number, just make it extremely unlikely valid_action_mask = torch.tensor(train_batch['valid_action_mask']) if 'seq_lens' in train_batch: diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index ee1284a28..5587a8091 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -160,6 +160,7 @@ def render(self, mode='human', observer=0): return super().render(mode, observer='global') + class RLlibMultiAgentWrapper(RLlibWrapper, MultiAgentEnv): def __init__(self, env_config): @@ -221,3 +222,4 @@ def step(self, action_dict: MultiAgentDict): return obs_map, reward_map, done_map, info_map + diff --git a/resources/games/Single-Player/GVGAI/random_butterflies.yaml b/resources/games/Single-Player/GVGAI/random_butterflies.yaml index 2ffb05070..305ddcc79 100644 --- a/resources/games/Single-Player/GVGAI/random_butterflies.yaml +++ b/resources/games/Single-Player/GVGAI/random_butterflies.yaml @@ -256,7 +256,7 @@ Actions: Delay: 3 Randomize: true Dst: - Object: wall + Object: [wall, cocoon] # If the spider moves into a butterfly it dies - Src: From bee1cf61173fab19d851d2d8bc09c6d959c72252 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 27 Feb 2021 10:22:04 +0000 Subject: [PATCH 17/34] fixing bug with removing objects that are owned by player 0 --- python/examples/rllib/rllib_single_agent.py | 14 +++++++------- src/Griddly/Core/Grid.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index c1890cf28..7b3ffccd0 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -15,7 +15,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + ray.init(num_gpus=1, local_mode=True) env_name = "ray-griddly-env" @@ -24,8 +24,8 @@ config = { 'framework': 'torch', - 'num_workers': 8, - 'num_envs_per_worker': 4, + 'num_workers': 1, + 'num_envs_per_worker': 1, 'model': { 'custom_model': 'GAP', @@ -34,16 +34,16 @@ 'env': env_name, 'env_config': { # Uncomment this line to apply invalid action masking - 'record_video_config': { - 'frequency': 10000 - }, + # 'record_video_config': { + # 'frequency': 10000 + # }, 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 3, 'max_steps': 1000, }, - 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) + #'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } stop = { diff --git a/src/Griddly/Core/Grid.cpp b/src/Griddly/Core/Grid.cpp index 0ce3215fd..aed52577b 100644 --- a/src/Griddly/Core/Grid.cpp +++ b/src/Griddly/Core/Grid.cpp @@ -430,7 +430,7 @@ bool Grid::removeObject(std::shared_ptr object) { invalidateLocation(location); // if we are removing a player's avatar - if(playerAvatars_.size() > 0 && playerAvatars_.at(playerId) == object) { + if(playerAvatars_.size() > 0 && playerId != 0 && playerAvatars_.at(playerId) == object) { spdlog::debug("Removing player {0} avatar {1}", playerId, objectName); playerAvatars_.erase(playerId); } From 6e97c95c83fe3c426cf583faf420d720e2d9c874 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sat, 27 Feb 2021 16:02:59 +0000 Subject: [PATCH 18/34] fixing issue with rewards not being correctly assigned to players --- bindings/wrapper/StepPlayerWrapper.cpp | 5 +- python/docs/sphinxdocs/game_docs.py | 3 + python/examples/gym/gym_human_player.py | 8 +- .../rllib/rllib_multiagent_taggers.py | 2 +- python/examples/rllib/rllib_single_agent.py | 12 +- python/griddly/util/rllib/wrappers.py | 4 +- src/Griddly/Core/GDY/Actions/Action.cpp | 7 +- src/Griddly/Core/GDY/Actions/Action.hpp | 5 +- src/Griddly/Core/GDY/Objects/Object.cpp | 50 ++++- src/Griddly/Core/GDY/Objects/Object.hpp | 13 +- src/Griddly/Core/GameProcess.cpp | 2 +- src/Griddly/Core/TurnBasedGameProcess.cpp | 3 +- .../Griddly/Core/GDY/Actions/ActionTest.cpp | 2 +- .../Griddly/Core/GDY/Objects/ObjectTest.cpp | 172 +++++++++++++++++- .../Griddly/Core/GDY/Actions/MockAction.hpp | 2 + 15 files changed, 253 insertions(+), 37 deletions(-) diff --git a/bindings/wrapper/StepPlayerWrapper.cpp b/bindings/wrapper/StepPlayerWrapper.cpp index 154144301..65a191b43 100644 --- a/bindings/wrapper/StepPlayerWrapper.cpp +++ b/bindings/wrapper/StepPlayerWrapper.cpp @@ -163,6 +163,7 @@ class Py_StepPlayerWrapper { std::shared_ptr buildAction(std::string actionName, std::vector actionArray) { auto actionInputsDefinition = gdyFactory_->findActionInputsDefinition(actionName); auto playerAvatar = player_->getAvatar(); + auto playerId = player_->getId(); auto inputMappings = actionInputsDefinition.inputMappings; @@ -176,7 +177,7 @@ class Py_StepPlayerWrapper { auto mapping = inputMappings[actionId]; auto vectorToDest = mapping.vectorToDest; auto orientationVector = mapping.orientationVector; - auto action = std::shared_ptr(new Action(gameProcess_->getGrid(), actionName, 0)); + auto action = std::shared_ptr(new Action(gameProcess_->getGrid(), actionName, playerId, 0)); action->init(playerAvatar, vectorToDest, orientationVector, actionInputsDefinition.relative); return action; @@ -195,7 +196,7 @@ class Py_StepPlayerWrapper { glm::ivec2 destinationLocation = sourceLocation + vector; - auto action = std::shared_ptr(new Action(gameProcess_->getGrid(), actionName, 0)); + auto action = std::shared_ptr(new Action(gameProcess_->getGrid(), actionName, playerId, 0)); action->init(sourceLocation, destinationLocation); return action; diff --git a/python/docs/sphinxdocs/game_docs.py b/python/docs/sphinxdocs/game_docs.py index c56cf2ec9..03d7e919e 100644 --- a/python/docs/sphinxdocs/game_docs.py +++ b/python/docs/sphinxdocs/game_docs.py @@ -141,6 +141,9 @@ def _generate_code_example(self, game_breakdown): # Replace with your own control algorithm! for s in range(1000):{single_step_code} env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() """ code_example_sphinx += 'The most basic way to create a Griddly Gym Environment. ' \ diff --git a/python/examples/gym/gym_human_player.py b/python/examples/gym/gym_human_player.py index 9899e5ca7..2e4171754 100644 --- a/python/examples/gym/gym_human_player.py +++ b/python/examples/gym/gym_human_player.py @@ -38,7 +38,7 @@ def _callback(prev_obs, obs, action, rew, env_done, info): # yaml_path = 'Single-Player/GVGAI/random_butterflies.yaml' # yaml_path = 'Single-Player/GVGAI/bait_keys.yaml' # yaml_path = 'Single-Player/Mini-Grid/minigrid-drunkdwarf.yaml' - yaml_path = 'Single-Player/Mini-Grid/minigrid-spiders.yaml' + # yaml_path = 'Single-Player/Mini-Grid/minigrid-spiders.yaml' # yaml_path = 'Single-Player/GVGAI/spider-nest.yaml' # yaml_path = 'Single-Player/GVGAI/cookmepasta.yaml' # yaml_path = 'Single-Player/GVGAI/clusters.yaml' @@ -55,11 +55,11 @@ def _callback(prev_obs, obs, action, rew, env_done, info): # yaml_path = '../resources/rataban.yaml' - level = 0 + level = 1 wrapper.build_gym_from_yaml(environment_name, yaml_path, - player_observer_type=gd.ObserverType.VECTOR, - global_observer_type=gd.ObserverType.VECTOR, level=level) + player_observer_type=gd.ObserverType.SPRITE_2D, + global_observer_type=gd.ObserverType.SPRITE_2D, level=level) env = gym.make(f'GDY-{environment_name}-v0') # env.enable_history(True) env.reset() diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index 719e3f75c..eec4a5be6 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -49,7 +49,7 @@ 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 1, - 'max_steps': 500, + 'max_steps': 1000, }, 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 7b3ffccd0..826a5c9c6 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -15,7 +15,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1, local_mode=True) + ray.init(num_gpus=1) env_name = "ray-griddly-env" @@ -24,8 +24,8 @@ config = { 'framework': 'torch', - 'num_workers': 1, - 'num_envs_per_worker': 1, + 'num_workers': 8, + 'num_envs_per_worker': 4, 'model': { 'custom_model': 'GAP', @@ -34,9 +34,9 @@ 'env': env_name, 'env_config': { # Uncomment this line to apply invalid action masking - # 'record_video_config': { - # 'frequency': 10000 - # }, + 'record_video_config': { + 'frequency': 10000 + }, 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers.py index 5587a8091..6656413bd 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers.py @@ -160,7 +160,6 @@ def render(self, mode='human', observer=0): return super().render(mode, observer='global') - class RLlibMultiAgentWrapper(RLlibWrapper, MultiAgentEnv): def __init__(self, env_config): @@ -221,5 +220,4 @@ def step(self, action_dict: MultiAgentDict): assert len(obs_map) == len(info_map) - return obs_map, reward_map, done_map, info_map - + return obs_map, reward_map, done_map, info_map \ No newline at end of file diff --git a/src/Griddly/Core/GDY/Actions/Action.cpp b/src/Griddly/Core/GDY/Actions/Action.cpp index 4075a8f26..ea9cf39fe 100644 --- a/src/Griddly/Core/GDY/Actions/Action.cpp +++ b/src/Griddly/Core/GDY/Actions/Action.cpp @@ -5,9 +5,10 @@ namespace griddly { -Action::Action(std::shared_ptr grid, std::string actionName, uint32_t delay) +Action::Action(std::shared_ptr grid, std::string actionName, uint32_t playerId, uint32_t delay) : actionName_(actionName), delay_(delay), + playerId_(playerId), grid_(grid) { } @@ -98,6 +99,10 @@ glm::ivec2 Action::getOrientationVector() const { std::string Action::getActionName() const { return actionName_; } +uint32_t Action::getOriginatingPlayerId() const { + return playerId_; +} + uint32_t Action::getDelay() const { return delay_; } diff --git a/src/Griddly/Core/GDY/Actions/Action.hpp b/src/Griddly/Core/GDY/Actions/Action.hpp index 9b0ba55f7..77d7d2cb0 100644 --- a/src/Griddly/Core/GDY/Actions/Action.hpp +++ b/src/Griddly/Core/GDY/Actions/Action.hpp @@ -34,7 +34,7 @@ struct ActionInputsDefinition { class Action { public: - Action(std::shared_ptr grid, std::string actionName, uint32_t delay = 0); + Action(std::shared_ptr grid, std::string actionName, uint32_t playerId, uint32_t delay = 0); // An action that is not tied to any specific units in the grid, these actions can be performed by the environment, or can be RTS input virtual void init(glm::ivec2 sourceLocation, glm::ivec2 destinationLocation); @@ -60,6 +60,8 @@ class Action { virtual glm::ivec2 getOrientationVector() const; + virtual uint32_t getOriginatingPlayerId() const; + // Delay an action virtual uint32_t getDelay() const; @@ -77,6 +79,7 @@ class Action { const std::string actionName_; const uint32_t delay_; const std::shared_ptr grid_; + const uint32_t playerId_ = 0; private: ActionMode actionMode_; diff --git a/src/Griddly/Core/GDY/Objects/Object.cpp b/src/Griddly/Core/GDY/Objects/Object.cpp index ceaa5b69c..a6b417686 100644 --- a/src/Griddly/Core/GDY/Objects/Object.cpp +++ b/src/Griddly/Core/GDY/Objects/Object.cpp @@ -204,10 +204,18 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou if (commandName == "reward") { auto value = commandArguments["0"].as(0); return [this, value](std::shared_ptr action) -> BehaviourResult { - // Find the player id of this object and give rewards to this player. - spdlog::debug("reward"); - return {false, {{*playerId_, value}}}; + // if the object has a player Id, the reward will be given to that object's player, + // otherwise the reward will be given to the player which has performed the action + auto rewardPlayer = getPlayerId() == 0 ? action->getOriginatingPlayerId() : getPlayerId(); + + if (rewardPlayer == 0) { + spdlog::warn("Misconfigured 'reward' for object '{0}' will not be assigned to a player.", action->getSourceObject()->getDescription()); + return {}; + } + + // Find the player id of this object and give rewards to this player. + return {false, {{rewardPlayer, value}}}; }; } @@ -327,7 +335,7 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto a = commandArguments["0"].as(); return [this, a](std::shared_ptr action) -> BehaviourResult { if (a == "_dest") { - std::shared_ptr cascadedAction = std::shared_ptr(new Action(grid_, action->getActionName(), action->getDelay())); + std::shared_ptr cascadedAction = std::shared_ptr(new Action(grid_, action->getActionName(), action->getOriginatingPlayerId(), action->getDelay())); cascadedAction->init(action->getDestinationObject(), action->getVectorToDest(), action->getOrientationVector(), false); @@ -353,10 +361,12 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou auto delay = commandArguments["Delay"].as(0); auto randomize = commandArguments["Randomize"].as(false); auto actionId = commandArguments["ActionId"].as(0); + auto executor = commandArguments["Executor"].as("action"); + + auto actionExecutor = getActionExecutorFromString(executor); // Resolve source object - return [this, actionName, delay, randomize, actionId](std::shared_ptr action) -> BehaviourResult { - std::shared_ptr newAction = std::shared_ptr(new Action(grid_, actionName, delay)); + return [this, actionName, delay, randomize, actionId, actionExecutor](std::shared_ptr action) -> BehaviourResult { InputMapping fallbackInputMapping; fallbackInputMapping.vectorToDest = action->getVectorToDest(); @@ -368,7 +378,21 @@ BehaviourFunction Object::instantiateBehaviour(std::string commandName, Behaviou inputMapping.vectorToDest = inputMapping.destinationLocation - getLocation(); } + auto execAsPlayerId = 0; + switch (actionExecutor) { + case ActionExecutor::ACTION_PLAYER_ID: + execAsPlayerId = action->getOriginatingPlayerId(); + break; + case ActionExecutor::OBJECT_PLAYER_ID: + execAsPlayerId = getPlayerId(); + break; + default: + break; + } + + std::shared_ptr newAction = std::shared_ptr(new Action(grid_, actionName, execAsPlayerId, delay)); newAction->init(shared_from_this(), inputMapping.vectorToDest, inputMapping.orientationVector, inputMapping.relative); + auto rewards = grid_->performActions(0, {newAction}); return {false, rewards}; @@ -567,7 +591,7 @@ std::vector> Object::getInitialActions() { auto inputMapping = getInputMapping(actionDefinition.actionName, actionDefinition.actionId, actionDefinition.randomize, InputMapping()); - auto action = std::shared_ptr(new Action(grid_, actionDefinition.actionName, actionDefinition.delay)); + auto action = std::shared_ptr(new Action(grid_, actionDefinition.actionName, 0, actionDefinition.delay)); if (inputMapping.mappedToGrid) { inputMapping.vectorToDest = inputMapping.destinationLocation - getLocation(); } @@ -630,4 +654,16 @@ std::unordered_set Object::getAvailableActionNames() const { return availableActionNames_; } +ActionExecutor Object::getActionExecutorFromString(std::string executorString) const { + if (executorString == "action") { + return ActionExecutor::ACTION_PLAYER_ID; + } else if (executorString == "object") { + return ActionExecutor::OBJECT_PLAYER_ID; + } else { + auto errorString = fmt::format("Invalid Action Executor choice '{0}'.", executorString); + spdlog::error(errorString); + throw std::invalid_argument(errorString); + } +} + } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/GDY/Objects/Object.hpp b/src/Griddly/Core/GDY/Objects/Object.hpp index d0c3ca2f2..0a74197cc 100644 --- a/src/Griddly/Core/GDY/Objects/Object.hpp +++ b/src/Griddly/Core/GDY/Objects/Object.hpp @@ -4,11 +4,11 @@ #include #include #include +#include #include #include #include #include -#include #include "../Actions/Direction.hpp" #include "ObjectVariable.hpp" @@ -36,7 +36,7 @@ struct SingleInputMapping { bool relative; bool internal; bool mappedToGrid; - + // if the action is relative to a source object glm::ivec2 vectorToDest{}; glm::ivec2 orientationVector{}; @@ -51,6 +51,11 @@ struct BehaviourResult { std::unordered_map rewards; }; +enum class ActionExecutor { + ACTION_PLAYER_ID, + OBJECT_PLAYER_ID, +}; + class Object : public std::enable_shared_from_this { public: virtual glm::ivec2 getLocation() const; @@ -72,7 +77,7 @@ class Object : public std::enable_shared_from_this { virtual DiscreteOrientation getObjectOrientation() const; virtual bool isPlayerAvatar() const; - + virtual void markAsPlayerAvatar(); // Set this object as a player avatar virtual bool isValidAction(std::shared_ptr action) const; @@ -149,6 +154,8 @@ class Object : public std::enable_shared_from_this { BehaviourFunction instantiateConditionalBehaviour(std::string commandName, BehaviourCommandArguments commandArguments, CommandList subCommands); std::string getStringMapValue(std::unordered_map map, std::string mapKey); + + ActionExecutor getActionExecutorFromString(std::string executorString) const; }; } // namespace griddly \ No newline at end of file diff --git a/src/Griddly/Core/GameProcess.cpp b/src/Griddly/Core/GameProcess.cpp index 1aa15ba39..4c4ec8e23 100644 --- a/src/Griddly/Core/GameProcess.cpp +++ b/src/Griddly/Core/GameProcess.cpp @@ -266,7 +266,7 @@ std::vector GameProcess::getAvailableActionIdsAtLocation(glm::ivec2 lo auto mapping = inputMapping.second; // Create an fake action to test for availability (and not duplicate a bunch of code) - auto potentialAction = std::shared_ptr(new Action(grid_, actionName)); + auto potentialAction = std::shared_ptr(new Action(grid_, actionName, 0)); potentialAction->init(srcObject, mapping.vectorToDest, mapping.orientationVector, relativeToSource); if (srcObject->isValidAction(potentialAction)) { diff --git a/src/Griddly/Core/TurnBasedGameProcess.cpp b/src/Griddly/Core/TurnBasedGameProcess.cpp index 81f70671f..0f8e039e4 100644 --- a/src/Griddly/Core/TurnBasedGameProcess.cpp +++ b/src/Griddly/Core/TurnBasedGameProcess.cpp @@ -139,11 +139,12 @@ std::shared_ptr TurnBasedGameProcess::clone() { auto vectorToDest = actionToCopy->getVectorToDest(); auto orientationVector = actionToCopy->getOrientationVector(); auto sourceObjectMapping = actionToCopy->getSourceObject(); + auto originatingPlayerId = actionToCopy->getOriginatingPlayerId(); auto clonedActionSourceObject = clonedObjectMapping[sourceObjectMapping]; // Clone the action - auto clonedAction = std::shared_ptr(new Action(clonedGrid, actionName, remainingTicks)); + auto clonedAction = std::shared_ptr(new Action(clonedGrid, actionName, originatingPlayerId, remainingTicks)); // The orientation and vector to dest are already modified from the first action in respect // to if this is a relative action, so relative is set to false here diff --git a/tests/src/Griddly/Core/GDY/Actions/ActionTest.cpp b/tests/src/Griddly/Core/GDY/Actions/ActionTest.cpp index f67a34e24..d57221b93 100644 --- a/tests/src/Griddly/Core/GDY/Actions/ActionTest.cpp +++ b/tests/src/Griddly/Core/GDY/Actions/ActionTest.cpp @@ -14,7 +14,7 @@ namespace griddly { TEST(ActionTest, initActionLocations) { auto mockGridPtr = std::shared_ptr(new MockGrid()); - std::shared_ptr action = std::shared_ptr(new Action(mockGridPtr, "testAction", 0)); + std::shared_ptr action = std::shared_ptr(new Action(mockGridPtr, "testAction", 0, 0)); action->init({0, 1}, {3, 4}); diff --git a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp index 61667a6e8..317f342a7 100644 --- a/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp +++ b/tests/src/Griddly/Core/GDY/Objects/ObjectTest.cpp @@ -21,6 +21,33 @@ using ::testing::ReturnRef; namespace griddly { +std::shared_ptr setupAction(std::string actionName, uint32_t originatingPlayerId, std::shared_ptr sourceObject, std::shared_ptr destObject) { + auto mockActionPtr = std::shared_ptr(new MockAction()); + + EXPECT_CALL(*mockActionPtr, getActionName()) + .WillRepeatedly(Return(actionName)); + + EXPECT_CALL(*mockActionPtr, getSourceObject()) + .WillRepeatedly(Return(sourceObject)); + + EXPECT_CALL(*mockActionPtr, getDestinationObject()) + .WillRepeatedly(Return(destObject)); + + EXPECT_CALL(*mockActionPtr, getSourceLocation()) + .WillRepeatedly(Return(sourceObject->getLocation())); + + EXPECT_CALL(*mockActionPtr, getDestinationLocation()) + .WillRepeatedly(Return(destObject->getLocation())); + + EXPECT_CALL(*mockActionPtr, getOriginatingPlayerId()) + .WillRepeatedly(Return(originatingPlayerId)); + + EXPECT_CALL(*mockActionPtr, getVectorToDest()) + .WillRepeatedly(Return(destObject->getLocation() - sourceObject->getLocation())); + + return mockActionPtr; +} + std::shared_ptr setupAction(std::string actionName, std::shared_ptr sourceObject, std::shared_ptr destObject) { auto mockActionPtr = std::shared_ptr(new MockAction()); @@ -39,6 +66,9 @@ std::shared_ptr setupAction(std::string actionName, std::shared_ptr< EXPECT_CALL(*mockActionPtr, getDestinationLocation()) .WillRepeatedly(Return(destObject->getLocation())); + EXPECT_CALL(*mockActionPtr, getOriginatingPlayerId()) + .WillRepeatedly(Return(1)); + EXPECT_CALL(*mockActionPtr, getVectorToDest()) .WillRepeatedly(Return(destObject->getLocation() - sourceObject->getLocation())); @@ -372,12 +402,13 @@ MATCHER_P3(SingletonMappedToGridMatcher, actionName, sourceObjectPtr, destinatio action->getDestinationLocation().y < destinationLocationRange.y; } -MATCHER_P3(SingletonActionVectorMatcher, actionName, sourceObjectPtr, vectorToDest, "") { +MATCHER_P4(SingletonActionVectorOriginatingPlayerMatcher, actionName, sourceObjectPtr, originatingPlayerId, vectorToDest, "") { auto action = arg[0]; return arg.size() == 1 && action->getActionName() == actionName && action->getSourceObject().get() == sourceObjectPtr.get() && - action->getVectorToDest() == vectorToDest; + action->getVectorToDest() == vectorToDest && + action->getOriginatingPlayerId() == originatingPlayerId; } TEST(ObjectTest, command_reward) { @@ -394,6 +425,20 @@ TEST(ObjectTest, command_reward) { verifyMocks(mockActionPtr); } +TEST(ObjectTest, command_reward_default_to_action_player_id) { + auto srcObjectPtr = setupObject(0, "srcObject", {}); + auto dstObjectPtr = setupObject(0, "dstObject", {}); + auto mockActionPtr = setupAction("action", 2, srcObjectPtr, dstObjectPtr); + + auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "reward", {{"0", _Y("10")}}, srcObjectPtr, dstObjectPtr); + auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "reward", {{"0", _Y("-10")}}, srcObjectPtr, dstObjectPtr); + + verifyCommandResult(srcResult, false, {{2, 10}}); + verifyCommandResult(dstResult, false, {{2, -10}}); + + verifyMocks(mockActionPtr); +} + TEST(ObjectTest, command_set) { auto srcObjectPtr = setupObject("srcObject", {{"test_param", _V(20)}}); auto dstObjectPtr = setupObject("dstObject", {{"test_param", _V(20)}}); @@ -757,11 +802,11 @@ TEST(ObjectTest, command_exec) { auto mockActionPtr = setupAction("do_exec", srcObjectPtr, dstObjectPtr); - EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", srcObjectPtr, glm::ivec2{0, -1}))) + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", srcObjectPtr, 1, glm::ivec2{0, -1}))) .Times(1) .WillOnce(Return(std::unordered_map{{2, 3}})); - EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", dstObjectPtr, glm::ivec2{0, -1}))) + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", dstObjectPtr, 1, glm::ivec2{0, -1}))) .Times(1) .WillOnce(Return(std::unordered_map{{10, 6}})); @@ -774,6 +819,121 @@ TEST(ObjectTest, command_exec) { verifyMocks(mockActionPtr, mockGridPtr); } +TEST(ObjectTest, command_exec_with_action_player_id) { + //* - Src: + //* Object: srcObject + //* Commands: + //* - exec: + //* Action: exec_action + //* ActionId: 2 + //* Executor: action + //* + //* Dst: + //* Object: dstObject + //* Commands: + //* - exec: + //* Action: exec_action + //* ActionId: 2 + //* Executor: action + //* + + auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); + auto mockGridPtr = mockGrid(); + auto srcObjectPtr = setupObject(2, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(10, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + + std::unordered_map mockInputDefinitions{ + {"exec_action", {{ + {1, {{-1, 0}, {-1, 0}, "Left"}}, + {2, {{0, -1}, {0, -1}, "Up"}}, + {3, {{1, 0}, {1, 0}, "Right"}}, + {4, {{0, 1}, {0, 1}, "Down"}}, + }, + false, + false}}}; + + EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()) + .Times(2) + .WillRepeatedly(Return(mockInputDefinitions)); + + auto mockActionPtr1 = setupAction("do_exec", 5, srcObjectPtr, dstObjectPtr); + auto mockActionPtr2 = setupAction("do_exec", 4, srcObjectPtr, dstObjectPtr); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", srcObjectPtr, 5, glm::ivec2{0, -1}))) + .Times(1) + .WillOnce(Return(std::unordered_map{{2, 3}})); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", dstObjectPtr, 4, glm::ivec2{0, -1}))) + .Times(1) + .WillOnce(Return(std::unordered_map{{10, 6}})); + + auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr1, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}, {"Executor", _Y("action")}}, srcObjectPtr, dstObjectPtr); + auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr2, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}, {"Executor", _Y("action")}}, srcObjectPtr, dstObjectPtr); + + verifyCommandResult(srcResult, false, {{2, 3}}); + verifyCommandResult(dstResult, false, {{10, 6}}); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockActionPtr1.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockActionPtr2.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); +} + +TEST(ObjectTest, command_exec_with_object_player_id) { + //* - Src: + //* Object: srcObject + //* Commands: + //* - exec: + //* Action: exec_action + //* ActionId: 2 + //* Executor: object + //* + //* Dst: + //* Object: dstObject + //* Commands: + //* - exec: + //* Action: exec_action + //* ActionId: 2 + //* Executor: object + //* + + auto mockObjectGenerator = std::shared_ptr(new MockObjectGenerator()); + auto mockGridPtr = mockGrid(); + auto srcObjectPtr = setupObject(2, "srcObject", glm::ivec2(0, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + auto dstObjectPtr = setupObject(10, "dstObject", glm::ivec2(1, 0), DiscreteOrientation(), {}, mockGridPtr, mockObjectGenerator); + + std::unordered_map mockInputDefinitions{ + {"exec_action", {{ + {1, {{-1, 0}, {-1, 0}, "Left"}}, + {2, {{0, -1}, {0, -1}, "Up"}}, + {3, {{1, 0}, {1, 0}, "Right"}}, + {4, {{0, 1}, {0, 1}, "Down"}}, + }, + false, + false}}}; + + EXPECT_CALL(*mockObjectGenerator, getActionInputDefinitions()) + .Times(2) + .WillRepeatedly(Return(mockInputDefinitions)); + + auto mockActionPtr = setupAction("do_exec", srcObjectPtr, dstObjectPtr); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", srcObjectPtr, 2, glm::ivec2{0, -1}))) + .Times(1) + .WillOnce(Return(std::unordered_map{{2, 3}})); + + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", dstObjectPtr, 10, glm::ivec2{0, -1}))) + .Times(1) + .WillOnce(Return(std::unordered_map{{10, 6}})); + + auto srcResult = addCommandsAndExecute(ActionBehaviourType::SOURCE, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}, {"Executor", _Y("object")}}, srcObjectPtr, dstObjectPtr); + auto dstResult = addCommandsAndExecute(ActionBehaviourType::DESTINATION, mockActionPtr, "exec", {{"Action", _Y("exec_action")}, {"ActionId", _Y(2)}, {"Executor", _Y("object")}}, srcObjectPtr, dstObjectPtr); + + verifyCommandResult(srcResult, false, {{2, 3}}); + verifyCommandResult(dstResult, false, {{10, 6}}); + + verifyMocks(mockActionPtr, mockGridPtr); +} + TEST(ObjectTest, command_exec_randomize) { //* - Src: //* Object: srcObject @@ -807,11 +967,11 @@ TEST(ObjectTest, command_exec_randomize) { .Times(2) .WillRepeatedly(Return(mockInputDefinitions)); - EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", srcObjectPtr, glm::ivec2(-1, 0)))) + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", srcObjectPtr, 1, glm::ivec2(-1, 0)))) .Times(1) .WillOnce(Return(std::unordered_map{{1, 3}})); - EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorMatcher("exec_action", dstObjectPtr, glm::ivec2(-1, 0)))) + EXPECT_CALL(*mockGridPtr, performActions(Eq(0), SingletonActionVectorOriginatingPlayerMatcher("exec_action", dstObjectPtr, 1, glm::ivec2(-1, 0)))) .Times(1) .WillOnce(Return(std::unordered_map{{1, 3}})); diff --git a/tests/src/Mocks/Griddly/Core/GDY/Actions/MockAction.hpp b/tests/src/Mocks/Griddly/Core/GDY/Actions/MockAction.hpp index 3893005a1..82e669d90 100644 --- a/tests/src/Mocks/Griddly/Core/GDY/Actions/MockAction.hpp +++ b/tests/src/Mocks/Griddly/Core/GDY/Actions/MockAction.hpp @@ -25,5 +25,7 @@ class MockAction : public Action { MOCK_METHOD(std::string, getActionName, (), (const)); MOCK_METHOD(std::string, getDescription, (), (const)); MOCK_METHOD(uint32_t, getDelay, (), (const)); + + MOCK_METHOD(uint32_t, getOriginatingPlayerId, (), (const)); }; } // namespace griddly \ No newline at end of file From 6aa401368e3ab63ea4b1552c25f96c1af73e33d3 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 1 Mar 2021 17:20:12 +0000 Subject: [PATCH 19/34] starting to work on gridnet support --- python/examples/rllib/rllib_RTS_Gridnet.py | 30 +-- .../rllib/rllib_multiagent_taggers.py | 13 +- python/examples/rllib/test_ma_tag_coins.yaml | 219 ++++++++++++++++++ python/griddly/GymWrapper.py | 5 +- python/griddly/util/rllib/__init__.py | 1 - .../util/rllib/torch/agents/gridnet_agent.py | 65 ++++-- .../torch/conditional_masking_distribution.py | 3 - .../torch/impala/im_vtrace_torch_policy.py | 2 +- .../util/rllib/torch/mixins/__init__.py | 0 .../invalid_action_policy_masking.py} | 1 + .../griddly/util/rllib/wrappers/__init__.py | 0 .../rllib/{wrappers.py => wrappers/core.py} | 56 +++-- python/griddly/util/rllib/wrappers/gridnet.py | 50 ++++ 13 files changed, 375 insertions(+), 70 deletions(-) create mode 100644 python/examples/rllib/test_ma_tag_coins.yaml create mode 100644 python/griddly/util/rllib/torch/mixins/__init__.py rename python/griddly/util/rllib/torch/{mixins.py => mixins/invalid_action_policy_masking.py} (99%) create mode 100644 python/griddly/util/rllib/wrappers/__init__.py rename python/griddly/util/rllib/{wrappers.py => wrappers/core.py} (81%) create mode 100644 python/griddly/util/rllib/wrappers/gridnet.py diff --git a/python/examples/rllib/rllib_RTS_Gridnet.py b/python/examples/rllib/rllib_RTS_Gridnet.py index 98522fce9..49bbb3b29 100644 --- a/python/examples/rllib/rllib_RTS_Gridnet.py +++ b/python/examples/rllib/rllib_RTS_Gridnet.py @@ -7,49 +7,53 @@ from ray.tune.registry import register_env from griddly import gd -from griddly.util.rllib import RLlibMultiAgentWrapper -from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.torch.agents.gridnet_agent import GridnetAgent from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer - - -class GridnetAgent(object): - pass +from griddly.util.rllib.wrappers.core import RLlibMultiAgentWrapper +from griddly.util.rllib.wrappers.gridnet import RLlibGridnetEnv if __name__ == '__main__': sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + ray.init(num_gpus=1, local_mode=True) env_name = 'ray-griddly-rts-env' test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') - register_env(env_name, RLlibMultiAgentWrapper) + # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play + def _env_create(env_config): + env = RLlibGridnetEnv(env_config) + return RLlibMultiAgentWrapper(env, env_config) + + register_env(env_name, _env_create) ModelCatalog.register_custom_model('GridnetAgent', GridnetAgent) config = { 'framework': 'torch', - 'num_workers': 8, + 'num_workers': 1, 'num_envs_per_worker': 1, + "_use_trajectory_view_api": False, 'model': { 'custom_model': 'GridnetAgent', 'custom_model_config': {} }, 'env': env_name, 'env_config': { - 'record_video_config': { - 'frequency': 100000 # number of rollouts - }, + 'invalid_action_masking': True, + # 'record_video_config': { + # 'frequency': 100000 # number of rollouts + # }, 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.ISOMETRIC, 'level': 1, 'max_steps': 1000, }, - 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) + #'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) } stop = { diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index eec4a5be6..ea994bd23 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -8,8 +8,8 @@ from ray.tune.registry import register_env from griddly import gd -from griddly.util.rllib import RLlibMultiAgentWrapper from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.wrappers.core import RLlibMultiAgentWrapper, RLlibEnv if __name__ == '__main__': sep = os.pathsep @@ -17,11 +17,16 @@ ray.init(num_gpus=1) - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_ma_tag.yaml') + test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_ma_tag_coins.yaml') env_name = 'ray-ma-grouped-env' - register_env(env_name, RLlibMultiAgentWrapper) + # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play + def _create_env(env_config): + env = RLlibEnv(env_config) + return RLlibMultiAgentWrapper(env, env_config) + + register_env(env_name, _create_env) ModelCatalog.register_custom_model('GAP', GAPAgent) @@ -48,7 +53,7 @@ 'yaml_file': test_path, 'global_observer_type': gd.ObserverType.SPRITE_2D, - 'level': 1, + 'level': 2, 'max_steps': 1000, }, 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) diff --git a/python/examples/rllib/test_ma_tag_coins.yaml b/python/examples/rllib/test_ma_tag_coins.yaml new file mode 100644 index 000000000..3ef17e1ca --- /dev/null +++ b/python/examples/rllib/test_ma_tag_coins.yaml @@ -0,0 +1,219 @@ +Version: "0.1" +Environment: + Name: Robot Battle + Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 12 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 5 + Width: 5 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W . . f2 . f12 . . W + W . . . . . . . W + W f1 . f3 . f10 . f11 W + W . . . . . . . W + W . . . . . . . W + W f4 . f5 . f7 . f8 W + W . . . . . . . W + W . . f6 . f9 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f12 . . W + W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W + W . . f6 . . . . . . . . . . . . . . f9 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f12 . . W + W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . W W W W W W . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c W . . . . . . W c . . . c . W + W . c . . . c W . . . . . . W c . . . c . W + W . c . . . c W . . . . . . W c . . . c . W + W . c . . . c W . . . . . . W c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . W W W W W W . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . c . . . c . . . . . . . . c . . . c . W + W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W + W . . f6 . . . . . . . . . . . . . . f9 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [src._playerId, dst._playerId] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: tagger + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - reward: 3 + Dst: + Object: coin + Commands: + - remove: true + +Objects: + - Name: coin + MapCharacter: c + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/jewel-3.png + Block2D: + - Shape: circle + Color: [0, 0.7, 0] + Scald: 1.0 + + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square \ No newline at end of file diff --git a/python/griddly/GymWrapper.py b/python/griddly/GymWrapper.py index e67a782dd..3a64713a6 100644 --- a/python/griddly/GymWrapper.py +++ b/python/griddly/GymWrapper.py @@ -105,7 +105,7 @@ def step(self, action): elif len(action) == self.player_count: - if np.ndim(action) == 1: + if np.ndim(action) == 1 or np.ndim(action) == 3: if isinstance(action[0], list) or isinstance(action[0], np.ndarray): # Multiple agents that can perform multiple actions in parallel # Used in RTS games @@ -125,6 +125,9 @@ def step(self, action): elif np.ndim(action) == 2: action_data = np.array(action, dtype=np.int32) reward, done, info = self.game.step_parallel(action_data) + else: + raise ValueError(f'The supplied action is in the wrong format for this environment.\n\n' + f'A valid example: {self.action_space.sample()}') else: raise ValueError(f'The supplied action is in the wrong format for this environment.\n\n' diff --git a/python/griddly/util/rllib/__init__.py b/python/griddly/util/rllib/__init__.py index ba1db8067..e69de29bb 100644 --- a/python/griddly/util/rllib/__init__.py +++ b/python/griddly/util/rllib/__init__.py @@ -1 +0,0 @@ -from griddly.util.rllib.wrappers import RLlibWrapper, RLlibMultiAgentWrapper \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/agents/gridnet_agent.py b/python/griddly/util/rllib/torch/agents/gridnet_agent.py index 31e753f01..dc9b5aaaf 100644 --- a/python/griddly/util/rllib/torch/agents/gridnet_agent.py +++ b/python/griddly/util/rllib/torch/agents/gridnet_agent.py @@ -1,8 +1,16 @@ from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 from torch import nn -import torch +import numpy as np -from griddly.util.rllib.torch.agents.global_average_pooling_agent import layer_init +from griddly.util.rllib.torch.agents.global_average_pooling_agent import layer_init, GlobalAvePool + + +def deconv_size(i, k, d, s, p, op): + return (i - 1) * s - 2 * p + d * (k - 1) + op + 1 + + +def conv_size(i, k, d, s, p): + return np.floor(1 + ((i + 2 * p - d * (k - 1) - 1) / s)) class GridnetAgent(TorchModelV2, nn.Module): @@ -10,45 +18,58 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): super().__init__(obs_space, action_space, num_outputs, model_config, name) nn.Module.__init__(self) - height = obs_space.original_space['obs'].shape[0] - width = obs_space.original_space['obs'].shape[1] + height = obs_space.shape[0] + width = obs_space.shape[1] + obs_channels = obs_space.shape[2] - grid_channels = 78 + grid_channels = np.sum(action_space.nvec[:int(action_space.shape[0] / (height * width))]) - self.encoder = nn.Sequential( - layer_init(nn.Conv2d(27, 32, kernel_size=5, padding=2)), + self._encoder = nn.Sequential( + nn.Conv2d(obs_channels, 32, kernel_size=3, padding=1), + nn.MaxPool2d(3, stride=1, padding=1), nn.ReLU(), - layer_init(nn.Conv2d(32, 64, kernel_size=5, padding=2)), + nn.Conv2d(32, 64, kernel_size=3, padding=1), + nn.MaxPool2d(3, stride=1, padding=1), nn.ReLU(), - layer_init(nn.Conv2d(128, 128, kernel_size=3, padding=1)), - nn.ReLU() + # nn.Conv2d(64, 128, kernel_size=3, padding=1), + # nn.MaxPool2d(3, stride=1, padding=1), + # nn.ReLU(), + # nn.Conv2d(128, 256, kernel_size=3, padding=1), + # nn.MaxPool2d(3, stride=1, padding=1), ) - self.actor = nn.Sequential( - layer_init(nn.Conv2d(128, grid_channels, kernel_size=1)), + self._decode = nn.Sequential( + # nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), + # nn.ReLU(), + # nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), + # nn.ReLU(), + nn.ConvTranspose2d(64, 32, 3, stride=1, padding=1), + nn.ReLU(), + nn.ConvTranspose2d(32, grid_channels, 3, stride=1, padding=1), + nn.ReLU(), + nn.Flatten(), ) self.critic = nn.Sequential( + GlobalAvePool(256), nn.Flatten(), - layer_init(nn.Linear(64 * height * width, 256)), + layer_init(nn.Linear(256, 256)), nn.ReLU(), - layer_init(nn.Linear(256, 1), std=1)) + layer_init(nn.Linear(256, 1), std=1) + ) def forward(self, input_dict, state, seq_lens): - input_obs = input_dict['obs']['obs'] - input_mask = input_dict['obs']['invalid_action_masks'] - self._encoded = self.encoder(input_obs) + input_obs = input_dict['obs'].permute(0, 3, 1, 2) + self._encoded = self._encoder(input_obs) # Value function value = self.critic(self._encoded) self._value = value.reshape(-1) # Logits for actions - logits = self.actor(self._encoded) - - masked_logits = logits + torch.log(input_mask) + logits = self._decode(self._encoded) - return masked_logits, state + return logits, state def value_function(self): - return self._value \ No newline at end of file + return self._value diff --git a/python/griddly/util/rllib/torch/conditional_masking_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_distribution.py index 7644f9d4c..a48e0e8bd 100644 --- a/python/griddly/util/rllib/torch/conditional_masking_distribution.py +++ b/python/griddly/util/rllib/torch/conditional_masking_distribution.py @@ -19,9 +19,6 @@ def __init__(self, dist_inputs, valid_action_trees, dist_class): def _mask_and_sample(self, options, logits): - if len(options) == 0: - print('boooo') - mask = torch.zeros([logits.shape[0]]) mask[options] = 1 diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py index f7948a392..20dcb7ea3 100644 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py @@ -11,7 +11,7 @@ from ray.rllib.utils.framework import try_import_torch from ray.rllib.utils.torch_ops import sequence_mask -from griddly.util.rllib.torch.mixins import InvalidActionMaskingPolicyMixin +from griddly.util.rllib.torch.mixins.invalid_action_policy_masking import InvalidActionMaskingPolicyMixin torch, nn = try_import_torch() diff --git a/python/griddly/util/rllib/torch/mixins/__init__.py b/python/griddly/util/rllib/torch/mixins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/torch/mixins.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py similarity index 99% rename from python/griddly/util/rllib/torch/mixins.py rename to python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py index 737274e82..3a4cfd359 100644 --- a/python/griddly/util/rllib/torch/mixins.py +++ b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py @@ -73,6 +73,7 @@ def compute_actions( else: valid_action_trees.append({0: {0: {0: [0]}}}) + exploration = TorchConditionalMaskingExploration(dist_inputs, valid_action_trees, self.dist_class) actions, masked_dist_actions, mask = exploration.get_actions_and_mask() diff --git a/python/griddly/util/rllib/wrappers/__init__.py b/python/griddly/util/rllib/wrappers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/wrappers.py b/python/griddly/util/rllib/wrappers/core.py similarity index 81% rename from python/griddly/util/rllib/wrappers.py rename to python/griddly/util/rllib/wrappers/core.py index 6656413bd..4e39c155d 100644 --- a/python/griddly/util/rllib/wrappers.py +++ b/python/griddly/util/rllib/wrappers/core.py @@ -21,7 +21,7 @@ class RecordingState(Enum): RECORDING = 4 -class RLlibWrapper(GymWrapper): +class RLlibEnv(GymWrapper): """ Wraps a Griddly environment for compatibility with RLLib. @@ -57,7 +57,7 @@ class RLlibWrapper(GymWrapper): def __init__(self, env_config): super().__init__(**env_config) - self._invalid_action_masking = env_config.get('invalid_action_masking', False) + self.invalid_action_masking = env_config.get('invalid_action_masking', False) self._record_video_config = env_config.get('record_video_config', None) super().reset() @@ -71,14 +71,6 @@ def __init__(self, env_config): self.set_transform() - def _transform_obs_space(self, observation_space): - - return gym.spaces.Box( - observation_space.low.transpose((1, 2, 0)), - observation_space.high.transpose((1, 2, 0)), - dtype=np.float, - ) - def _get_player_action_tree(self, player_id): valid_action_tree = defaultdict(lambda: defaultdict(lambda: defaultdict(defaultdict))) @@ -93,10 +85,10 @@ def _build_valid_action_trees(self): if self.player_count > 0: for p in range(self.player_count): - player_valid_action_trees.append({'valid_action_tree': self._get_player_action_tree(p + 1)}) + player_valid_action_trees.append(self._get_player_action_tree(p + 1)) else: - player_valid_action_trees.append({'valid_action_tree': self._get_player_action_tree(1)}) + player_valid_action_trees.append(self._get_player_action_tree(1)) return player_valid_action_trees @@ -140,11 +132,22 @@ def set_transform(self): self.observation_space = self.observation_space[0] self.action_space = self.action_space[0] - self.observation_space = self._transform_obs_space(self.observation_space) + self.observation_space = gym.spaces.Box( + self.observation_space.low.transpose((1, 2, 0)), + self.observation_space.high.transpose((1, 2, 0)), + dtype=np.float, + ) + + self.height = self.observation_space.shape[0] + self.width = self.observation_space.shape[1] def reset(self, **kwargs): observation = super().reset(**kwargs) self.set_transform() + + if self.invalid_action_masking: + self.last_valid_action_trees = self._build_valid_action_trees() + return self._transform(observation) def step(self, action): @@ -154,16 +157,20 @@ def step(self, action): self._env_steps += 1 + if self.invalid_action_masking: + self.last_valid_action_trees = self._build_valid_action_trees() + info['valid_action_trees'] = self.last_valid_action_trees + return self._transform(observation), reward, done, info def render(self, mode='human', observer=0): return super().render(mode, observer='global') -class RLlibMultiAgentWrapper(RLlibWrapper, MultiAgentEnv): +class RLlibMultiAgentWrapper(gym.Wrapper, MultiAgentEnv): - def __init__(self, env_config): - super().__init__(env_config) + def __init__(self, env, env_config): + super().__init__(env) self._player_done_variable = env_config.get('player_done_variable', None) @@ -173,11 +180,11 @@ def __init__(self, env_config): assert self.player_count > 1, 'RLlibMultiAgentWrapper can only be used with environments that have multiple agents' def _to_multi_agent_map(self, data): - return {a: data[a-1] for a in self._active_agents} + return {a: data[a - 1] for a in self._active_agents} def reset(self, **kwargs): obs = super().reset(**kwargs) - self._active_agents.update([a+1 for a in range(self.player_count)]) + self._active_agents.update([a + 1 for a in range(self.player_count)]) return self._to_multi_agent_map(obs) def _resolve_player_done_variable(self): @@ -187,9 +194,9 @@ def _resolve_player_done_variable(self): def step(self, action_dict: MultiAgentDict): actions_array = np.zeros((self.player_count, *self.action_space.shape)) for agent_id, action in action_dict.items(): - actions_array[agent_id-1] = action + actions_array[agent_id - 1] = action - obs, reward, all_done, _ = super().step(actions_array) + obs, reward, all_done, info = super().step(actions_array) done_map = {'__all__': all_done} @@ -202,8 +209,8 @@ def step(self, action_dict: MultiAgentDict): for p in range(self.player_count): done_map[p] = False - if self._invalid_action_masking: - info_map = self._to_multi_agent_map(self._build_valid_action_trees()) + if self.invalid_action_masking: + info_map = self._to_multi_agent_map(self.last_valid_action_trees) else: info_map = self._to_multi_agent_map(defaultdict(dict)) @@ -216,8 +223,7 @@ def step(self, action_dict: MultiAgentDict): self._active_agents.discard(agent_id) assert len(obs_map) == len(reward_map) - assert len(obs_map) == len(done_map)-1 + assert len(obs_map) == len(done_map) - 1 assert len(obs_map) == len(info_map) - - return obs_map, reward_map, done_map, info_map \ No newline at end of file + return obs_map, reward_map, done_map, info_map diff --git a/python/griddly/util/rllib/wrappers/gridnet.py b/python/griddly/util/rllib/wrappers/gridnet.py new file mode 100644 index 000000000..08d2a6554 --- /dev/null +++ b/python/griddly/util/rllib/wrappers/gridnet.py @@ -0,0 +1,50 @@ +from gym.spaces import MultiDiscrete +import numpy as np +from griddly.util.rllib.wrappers.core import RLlibEnv + + +class RLlibGridnetEnv(RLlibEnv): + + def __init__(self, env_config): + super().__init__(env_config) + + def set_transform(self): + """ + :return: + """ + + super().set_transform() + + num_grid_locations = self.width * self.height + cell_discrete_action_shape = self.action_space.nvec[2:] + + self.num_action_parts = 1 + if self.action_count > 1: + self.num_action_parts += 1 + + self.num_action_logits = np.sum(cell_discrete_action_shape) + + cell_multi_discretes = [] + for g in range(num_grid_locations): + cell_multi_discretes.extend(cell_discrete_action_shape) + + self.action_space = MultiDiscrete(cell_multi_discretes) + + def step(self, action): + # Un-grid the actions + + grid_actions = action.reshape(-1, self.num_action_parts, self.width, self.height) + + # We have a HxW grid of actions, but we only need the actions for the valid locations in the grid, + # so we pull them out + multi_actions = [] + for p in range(self.player_count): + unit_actions = [] + x_tree = self.last_valid_action_trees[p] + for x, y_tree in x_tree.items(): + for y, _ in y_tree.items(): + unit_action = grid_actions[p, :, x, y] + unit_actions.append([x, y, *unit_action]) + multi_actions.append(unit_actions) + + return super().step(multi_actions) From 1897f9e91f6a390c67534559c80dd7b109d17497 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 1 Mar 2021 18:40:57 +0000 Subject: [PATCH 20/34] optimizing out unecessary calculations for gridnet categorical stuff --- .../util/rllib/torch/agents/gridnet_agent.py | 4 +- ...onditional_masking_gridnet_distribution.py | 72 +++++++++++++++++++ .../mixins/invalid_action_policy_masking.py | 21 ++++-- 3 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py diff --git a/python/griddly/util/rllib/torch/agents/gridnet_agent.py b/python/griddly/util/rllib/torch/agents/gridnet_agent.py index dc9b5aaaf..fdedee433 100644 --- a/python/griddly/util/rllib/torch/agents/gridnet_agent.py +++ b/python/griddly/util/rllib/torch/agents/gridnet_agent.py @@ -22,7 +22,7 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): width = obs_space.shape[1] obs_channels = obs_space.shape[2] - grid_channels = np.sum(action_space.nvec[:int(action_space.shape[0] / (height * width))]) + self.grid_channels = np.sum(action_space.nvec[:int(action_space.shape[0] / (height * width))]) self._encoder = nn.Sequential( nn.Conv2d(obs_channels, 32, kernel_size=3, padding=1), @@ -45,7 +45,7 @@ def __init__(self, obs_space, action_space, num_outputs, model_config, name): # nn.ReLU(), nn.ConvTranspose2d(64, 32, 3, stride=1, padding=1), nn.ReLU(), - nn.ConvTranspose2d(32, grid_channels, 3, stride=1, padding=1), + nn.ConvTranspose2d(32, self.grid_channels, 3, stride=1, padding=1), nn.ReLU(), nn.Flatten(), ) diff --git a/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py new file mode 100644 index 000000000..5d8115197 --- /dev/null +++ b/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py @@ -0,0 +1,72 @@ +from typing import Union +import numpy as np +import torch +from torch.distributions import Categorical + + +class TorchConditionalMaskingGridnetExploration(): + + def __init__(self, model, dist_inputs, valid_action_trees, dist_class): + self._valid_action_trees = valid_action_trees + self._dist_class = dist_class + + self._num_inputs = dist_inputs.shape[0] + self._action_space_shape = dist_class.keywords['input_lens'] + self._num_action_logits = np.sum(self._action_space_shape) + self._num_action_parts = len(self._action_space_shape) + + self._dist_inputs_reshaped = dist_inputs.reshape(-1, model.grid_channels, model.width, model.height) + + def _mask_and_sample(self, options, logits): + + mask = torch.zeros([logits.shape[0]]) + mask[options] = 1 + + logits += torch.log(mask) + dist = Categorical(logits=logits) + sampled = dist.sample() + + return sampled, logits, mask + + def get_actions_and_mask(self): + + actions = torch.zeros([self._num_inputs, self._num_action_parts]) + masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) + mask = torch.zeros([self._num_inputs, self._num_action_logits]) + + for i in range(self._num_inputs): + if len(self._valid_action_trees) >= 1: + + subtree = self._valid_action_trees[i] + subtree_options = list(subtree.keys()) + + # In the case there are no available actions for the player + if len(subtree_options) == 0: + subtree = {0: {0: {0: [0]}}} + subtree_options = [0] + + mask_offset = 0 + for a in range(self._num_action_parts): + dist_part = self._inputs_split[a] + sampled, masked_logits_part, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) + + # Set the action and the mask for each part of the action + actions[i, a] = sampled + masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part + mask[i, mask_offset:mask_offset + self._action_space_shape[a]] = mask_part + + if mask_part.sum() == 0: + raise RuntimeError('mask calculated incorrectly') + + mask_offset += self._action_space_shape[a] + + if isinstance(subtree, dict): + subtree = subtree[int(sampled)] + if isinstance(subtree, dict): + subtree_options = list(subtree.keys()) + else: + # Leaf nodes with action_id list + subtree_options = subtree + + + return actions, masked_logits, mask diff --git a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py index 3a4cfd359..9c40c2320 100644 --- a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py +++ b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py @@ -12,6 +12,7 @@ import torch from griddly.util.rllib.torch.conditional_masking_distribution import TorchConditionalMaskingExploration +from griddly.util.rllib.torch.conditional_masking_gridnet_distribution import TorchConditionalMaskingGridnetExploration class InvalidActionMaskingPolicyMixin: @@ -34,7 +35,6 @@ def compute_actions( **kwargs) -> \ Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: - if not self.config['env_config'].get('invalid_action_masking', False): raise RuntimeError('invalid_action_masking must be set to True in env_config to use this mixin') @@ -58,13 +58,12 @@ def compute_actions( for s in (state_batches or []) ] - # Call the exploration before_compute_actions hook. self.exploration.before_compute_actions( explore=explore, timestep=timestep) dist_inputs, state_out = self.model(input_dict, state_batches, - seq_lens) + seq_lens) # Extract the tree from the info batch valid_action_trees = [] for info in info_batch: @@ -73,8 +72,20 @@ def compute_actions( else: valid_action_trees.append({0: {0: {0: [0]}}}) + if hasattr(self.model, 'grid_channels'): + exploration = TorchConditionalMaskingGridnetExploration( + self.model, + dist_inputs, + valid_action_trees, + self.dist_class + ) + else: + exploration = TorchConditionalMaskingExploration( + dist_inputs, + valid_action_trees, + self.dist_class + ) - exploration = TorchConditionalMaskingExploration(dist_inputs, valid_action_trees, self.dist_class) actions, masked_dist_actions, mask = exploration.get_actions_and_mask() masked_action_dist = self.dist_class(masked_dist_actions, self.model) @@ -103,5 +114,3 @@ def compute_actions( self.global_timestep += len(input_dict[SampleBatch.CUR_OBS]) return convert_to_non_torch_type((actions, state_out, extra_fetches)) - - From e68a29135ebee07d677a6a16875846b04e79bc92 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 2 Mar 2021 17:50:14 +0000 Subject: [PATCH 21/34] more progress on gridnet --- python/examples/rllib/rllib_RTS_UAS.py | 15 +-- .../util/rllib/torch/agents/gridnet_agent.py | 19 ++-- ....py => conditional_masking_exploration.py} | 17 ++-- ...onditional_masking_gridnet_distribution.py | 72 -------------- ...conditional_masking_gridnet_exploration.py | 97 +++++++++++++++++++ .../mixins/invalid_action_policy_masking.py | 25 ++--- python/griddly/util/rllib/wrappers/core.py | 4 +- 7 files changed, 136 insertions(+), 113 deletions(-) rename python/griddly/util/rllib/torch/{conditional_masking_distribution.py => conditional_masking_exploration.py} (80%) delete mode 100644 python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py create mode 100644 python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py diff --git a/python/examples/rllib/rllib_RTS_UAS.py b/python/examples/rllib/rllib_RTS_UAS.py index 1c1e98381..9f7287044 100644 --- a/python/examples/rllib/rllib_RTS_UAS.py +++ b/python/examples/rllib/rllib_RTS_UAS.py @@ -7,28 +7,31 @@ from ray.tune.registry import register_env from griddly import gd -from griddly.util.rllib import RLlibMultiAgentWrapper from griddly.util.rllib.torch import GAPAgent from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer - +from griddly.util.rllib.wrappers.core import RLlibEnv, RLlibMultiAgentWrapper if __name__ == '__main__': sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1, local_mode=True) + ray.init(num_gpus=1) env_name = 'ray-griddly-rts-env' test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') - register_env(env_name, RLlibMultiAgentWrapper) + def _env_create(env_config): + env = RLlibEnv(env_config) + return RLlibMultiAgentWrapper(env, env_config) + + register_env(env_name, _env_create) ModelCatalog.register_custom_model('GAP', GAPAgent) config = { 'framework': 'torch', - 'num_workers': 1, - 'num_envs_per_worker': 1, + 'num_workers': 8, + 'num_envs_per_worker': 4, # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, diff --git a/python/griddly/util/rllib/torch/agents/gridnet_agent.py b/python/griddly/util/rllib/torch/agents/gridnet_agent.py index fdedee433..89c60b623 100644 --- a/python/griddly/util/rllib/torch/agents/gridnet_agent.py +++ b/python/griddly/util/rllib/torch/agents/gridnet_agent.py @@ -5,27 +5,20 @@ from griddly.util.rllib.torch.agents.global_average_pooling_agent import layer_init, GlobalAvePool -def deconv_size(i, k, d, s, p, op): - return (i - 1) * s - 2 * p + d * (k - 1) + op + 1 - - -def conv_size(i, k, d, s, p): - return np.floor(1 + ((i + 2 * p - d * (k - 1) - 1) / s)) - - class GridnetAgent(TorchModelV2, nn.Module): def __init__(self, obs_space, action_space, num_outputs, model_config, name): super().__init__(obs_space, action_space, num_outputs, model_config, name) nn.Module.__init__(self) - height = obs_space.shape[0] - width = obs_space.shape[1] - obs_channels = obs_space.shape[2] + self.height = obs_space.shape[0] + self.width = obs_space.shape[1] + self.observation_channels = obs_space.shape[2] - self.grid_channels = np.sum(action_space.nvec[:int(action_space.shape[0] / (height * width))]) + self.grid_action_shape = action_space.nvec[:int(action_space.shape[0] / (self.height * self.height))] + self.grid_channels = np.sum(self.grid_action_shape) self._encoder = nn.Sequential( - nn.Conv2d(obs_channels, 32, kernel_size=3, padding=1), + nn.Conv2d(self.observation_channels, 32, kernel_size=3, padding=1), nn.MaxPool2d(3, stride=1, padding=1), nn.ReLU(), nn.Conv2d(32, 64, kernel_size=3, padding=1), diff --git a/python/griddly/util/rllib/torch/conditional_masking_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_exploration.py similarity index 80% rename from python/griddly/util/rllib/torch/conditional_masking_distribution.py rename to python/griddly/util/rllib/torch/conditional_masking_exploration.py index a48e0e8bd..fa1f147b6 100644 --- a/python/griddly/util/rllib/torch/conditional_masking_distribution.py +++ b/python/griddly/util/rllib/torch/conditional_masking_exploration.py @@ -1,4 +1,3 @@ -from typing import Union import numpy as np import torch from torch.distributions import Categorical @@ -6,7 +5,7 @@ class TorchConditionalMaskingExploration(): - def __init__(self, dist_inputs, valid_action_trees, dist_class): + def __init__(self, model, dist_class, dist_inputs, valid_action_trees): self._valid_action_trees = valid_action_trees self._dist_class = dist_class @@ -25,14 +24,16 @@ def _mask_and_sample(self, options, logits): logits += torch.log(mask) dist = Categorical(logits=logits) sampled = dist.sample() + logp = dist.log_prob(sampled) - return sampled, logits, mask + return sampled, logits, logp, mask def get_actions_and_mask(self): actions = torch.zeros([self._num_inputs, self._num_action_parts]) masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) mask = torch.zeros([self._num_inputs, self._num_action_logits]) + logp_sums = torch.zeros([self._num_inputs]) for i in range(self._num_inputs): if len(self._valid_action_trees) >= 1: @@ -45,16 +46,19 @@ def get_actions_and_mask(self): subtree = {0: {0: {0: [0]}}} subtree_options = [0] + logp_parts = torch.zeros([self._num_action_parts]) mask_offset = 0 for a in range(self._num_action_parts): dist_part = self._inputs_split[a] - sampled, masked_logits_part, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) + sampled, masked_part_logits, logp, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) # Set the action and the mask for each part of the action actions[i, a] = sampled - masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part + masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_part_logits mask[i, mask_offset:mask_offset + self._action_space_shape[a]] = mask_part + logp_parts[a] = logp + if mask_part.sum() == 0: raise RuntimeError('mask calculated incorrectly') @@ -68,5 +72,6 @@ def get_actions_and_mask(self): # Leaf nodes with action_id list subtree_options = subtree + logp_sums[i] = torch.sum(logp_parts) - return actions, masked_logits, mask + return actions, masked_logits, logp_sums, mask diff --git a/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py b/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py deleted file mode 100644 index 5d8115197..000000000 --- a/python/griddly/util/rllib/torch/conditional_masking_gridnet_distribution.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Union -import numpy as np -import torch -from torch.distributions import Categorical - - -class TorchConditionalMaskingGridnetExploration(): - - def __init__(self, model, dist_inputs, valid_action_trees, dist_class): - self._valid_action_trees = valid_action_trees - self._dist_class = dist_class - - self._num_inputs = dist_inputs.shape[0] - self._action_space_shape = dist_class.keywords['input_lens'] - self._num_action_logits = np.sum(self._action_space_shape) - self._num_action_parts = len(self._action_space_shape) - - self._dist_inputs_reshaped = dist_inputs.reshape(-1, model.grid_channels, model.width, model.height) - - def _mask_and_sample(self, options, logits): - - mask = torch.zeros([logits.shape[0]]) - mask[options] = 1 - - logits += torch.log(mask) - dist = Categorical(logits=logits) - sampled = dist.sample() - - return sampled, logits, mask - - def get_actions_and_mask(self): - - actions = torch.zeros([self._num_inputs, self._num_action_parts]) - masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) - mask = torch.zeros([self._num_inputs, self._num_action_logits]) - - for i in range(self._num_inputs): - if len(self._valid_action_trees) >= 1: - - subtree = self._valid_action_trees[i] - subtree_options = list(subtree.keys()) - - # In the case there are no available actions for the player - if len(subtree_options) == 0: - subtree = {0: {0: {0: [0]}}} - subtree_options = [0] - - mask_offset = 0 - for a in range(self._num_action_parts): - dist_part = self._inputs_split[a] - sampled, masked_logits_part, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) - - # Set the action and the mask for each part of the action - actions[i, a] = sampled - masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_logits_part - mask[i, mask_offset:mask_offset + self._action_space_shape[a]] = mask_part - - if mask_part.sum() == 0: - raise RuntimeError('mask calculated incorrectly') - - mask_offset += self._action_space_shape[a] - - if isinstance(subtree, dict): - subtree = subtree[int(sampled)] - if isinstance(subtree, dict): - subtree_options = list(subtree.keys()) - else: - # Leaf nodes with action_id list - subtree_options = subtree - - - return actions, masked_logits, mask diff --git a/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py b/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py new file mode 100644 index 000000000..a1b55a0a5 --- /dev/null +++ b/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py @@ -0,0 +1,97 @@ +import torch +from torch.distributions import Categorical + + +class TorchConditionalMaskingGridnetExploration(): + + def __init__(self, model, dist_class, dist_inputs, valid_action_trees): + self._valid_action_trees = valid_action_trees + self._dist_class = dist_class + + self._num_inputs = dist_inputs.shape[0] + + self._width = model.width + self._height = model.height + self._grid_action_shape = model.grid_action_shape + self._grid_action_parts = len(self._grid_action_shape) + self._grid_channels = model.grid_channels + + self._dist_inputs_reshaped = dist_inputs.reshape(-1, self._grid_channels, self._width, self._height) + + def _mask_and_sample(self, options, logits): + + mask = torch.zeros([logits.shape[0]]) + mask[options] = 1 + + logits += torch.log(mask) + dist = Categorical(logits=logits) + sampled = dist.sample() + logp = dist.log_prob(sampled) + + return sampled, logits, logp, mask + + def get_actions_and_mask(self): + + actions = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) + masked_logits = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) + mask = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) + logp_sums = torch.zeros([self._num_inputs]) + + # Initialize the masks to NOP + mask[:, 0, :, :] = 1 + for action_logit_size in self._grid_action_shape[:-1]: + mask[:, action_logit_size, :, :] = 0 + + for i in range(self._num_inputs): + if len(self._valid_action_trees) >= 1: + + x_tree = self._valid_action_trees[i] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + + # only bother with calculating actions for things that are possible in the grid + + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + dist_input = self._dist_inputs_reshaped[i, :, x, y] + dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) + + logp_parts = torch.zeros([self._grid_action_parts]) + mask_offset = 0 + for a in range(self._grid_action_parts): + dist_part = dist_input_split[a] + + sampled, masked_part_logits, logp, mask_part = self._mask_and_sample( + subtree_options, + dist_part + ) + + # Set the action and the mask for each part of the action + logit_end = mask_offset + self._grid_action_shape[a] + actions[i, a] = sampled + masked_logits[i, mask_offset:logit_end, x, y] = masked_part_logits + mask[i, mask_offset:logit_end, x, y] = mask_part + + logp_parts[a] = logp + + if mask_part.sum() == 0: + raise RuntimeError('mask calculated incorrectly') + + mask_offset += self._grid_action_shape[a] + + if isinstance(subtree, dict): + subtree = subtree[int(sampled)] + if isinstance(subtree, dict): + subtree_options = list(subtree.keys()) + else: + # Leaf nodes with action_id list + subtree_options = subtree + + logp_sums[i] = torch.sum(logp_parts) + + return actions.flatten(1), masked_logits.flatten(1), logp_sums, mask.flatten(1) diff --git a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py index 9c40c2320..979d2904c 100644 --- a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py +++ b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py @@ -11,8 +11,8 @@ import numpy as np import torch -from griddly.util.rllib.torch.conditional_masking_distribution import TorchConditionalMaskingExploration -from griddly.util.rllib.torch.conditional_masking_gridnet_distribution import TorchConditionalMaskingGridnetExploration +from griddly.util.rllib.torch.conditional_masking_exploration import TorchConditionalMaskingExploration +from griddly.util.rllib.torch.conditional_masking_gridnet_exploration import TorchConditionalMaskingGridnetExploration class InvalidActionMaskingPolicyMixin: @@ -75,34 +75,29 @@ def compute_actions( if hasattr(self.model, 'grid_channels'): exploration = TorchConditionalMaskingGridnetExploration( self.model, + self.dist_class, dist_inputs, valid_action_trees, - self.dist_class ) else: exploration = TorchConditionalMaskingExploration( + self.model, + self.dist_class, dist_inputs, valid_action_trees, - self.dist_class ) - actions, masked_dist_actions, mask = exploration.get_actions_and_mask() - - masked_action_dist = self.dist_class(masked_dist_actions, self.model) - - logp = masked_action_dist.logp(actions) + actions, masked_logits, logp, mask = exploration.get_actions_and_mask() input_dict[SampleBatch.ACTIONS] = actions - # Add default and custom fetches. - extra_fetches = self.extra_action_out(input_dict, state_batches, - self.model, masked_action_dist) - - extra_fetches['valid_action_mask'] = mask + extra_fetches = { + 'valid_action_mask': mask, + } # Action-dist inputs. if dist_inputs is not None: - extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_dist_actions + extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_logits # Action-logp and action-prob. if logp is not None: diff --git a/python/griddly/util/rllib/wrappers/core.py b/python/griddly/util/rllib/wrappers/core.py index 4e39c155d..55fc918ca 100644 --- a/python/griddly/util/rllib/wrappers/core.py +++ b/python/griddly/util/rllib/wrappers/core.py @@ -210,7 +210,9 @@ def step(self, action_dict: MultiAgentDict): done_map[p] = False if self.invalid_action_masking: - info_map = self._to_multi_agent_map(self.last_valid_action_trees) + info_map = self._to_multi_agent_map([ + {'valid_action_tree': valid_action_tree} for valid_action_tree in info['valid_action_trees'] + ]) else: info_map = self._to_multi_agent_map(defaultdict(dict)) From 4c2d728ba17c7548e611d2ea035fc930f2c52be6 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Wed, 3 Mar 2021 17:48:06 +0000 Subject: [PATCH 22/34] one day i will get this working and i will praise odin for his gift --- python/examples/rllib/rllib_RTS_UAS.py | 6 +- ...conditional_masking_gridnet_exploration.py | 2 +- .../rllib/torch/impala/im_vtrace_torch.py | 390 ++++++++++++++++++ .../torch/impala/im_vtrace_torch_policy.py | 133 +++++- .../mixins/invalid_action_policy_masking.py | 31 +- ...gridnet_masked_categorical_distribution.py | 176 ++++++++ 6 files changed, 724 insertions(+), 14 deletions(-) create mode 100644 python/griddly/util/rllib/torch/impala/im_vtrace_torch.py create mode 100644 python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py diff --git a/python/examples/rllib/rllib_RTS_UAS.py b/python/examples/rllib/rllib_RTS_UAS.py index 9f7287044..2ecd4e7f5 100644 --- a/python/examples/rllib/rllib_RTS_UAS.py +++ b/python/examples/rllib/rllib_RTS_UAS.py @@ -15,7 +15,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + ray.init(num_gpus=1, local_mode=True) env_name = 'ray-griddly-rts-env' @@ -30,8 +30,8 @@ def _env_create(env_config): config = { 'framework': 'torch', - 'num_workers': 8, - 'num_envs_per_worker': 4, + 'num_workers': 1, + 'num_envs_per_worker': 1, # Must be set to false to use the InvalidActionMaskingPolicyMixin "_use_trajectory_view_api": False, diff --git a/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py b/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py index a1b55a0a5..83d83846e 100644 --- a/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py +++ b/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py @@ -92,6 +92,6 @@ def get_actions_and_mask(self): # Leaf nodes with action_id list subtree_options = subtree - logp_sums[i] = torch.sum(logp_parts) + logp_sums[i] += torch.sum(logp_parts) return actions.flatten(1), masked_logits.flatten(1), logp_sums, mask.flatten(1) diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py new file mode 100644 index 000000000..4bf855db7 --- /dev/null +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py @@ -0,0 +1,390 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""PyTorch version of the functions to compute V-trace off-policy actor critic +targets. + +For details and theory see: + +"IMPALA: Scalable Distributed Deep-RL with +Importance Weighted Actor-Learner Architectures" +by Espeholt, Soyer, Munos et al. + +See https://arxiv.org/abs/1802.01561 for the full paper. + +In addition to the original paper's code, changes have been made +to support MultiDiscrete action spaces. behaviour_policy_logits, +target_policy_logits and actions parameters in the entry point +multi_from_logits method accepts lists of tensors instead of just +tensors. +""" + +from ray.rllib.agents.impala.vtrace_tf import VTraceFromLogitsReturns, \ + VTraceReturns +from ray.rllib.models.torch.torch_action_dist import TorchCategorical +from ray.rllib.utils import force_list +from ray.rllib.utils.framework import try_import_torch +from ray.rllib.utils.torch_ops import convert_to_torch_tensor +from torch.distributions import Categorical + +torch, nn = try_import_torch() + + +def log_probs_from_logits_and_actions(policy_logits, + actions, + dist_class=TorchCategorical, + model=None): + return multi_log_probs_from_logits_and_actions([policy_logits], [actions], + dist_class, model)[0] + + +def multi_log_probs_from_logits_and_actions(policy_logits, actions, dist_class, + model, valid_action_trees=None): + """Computes action log-probs from policy logits and actions. + + In the notation used throughout documentation and comments, T refers to the + time dimension ranging from 0 to T-1. B refers to the batch size and + ACTION_SPACE refers to the list of numbers each representing a number of + actions. + + Args: + policy_logits: A list with length of ACTION_SPACE of float32 + tensors of shapes [T, B, ACTION_SPACE[0]], ..., + [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities + parameterizing a softmax policy. + actions: A list with length of ACTION_SPACE of tensors of shapes + [T, B, ...], ..., [T, B, ...] + with actions. + dist_class: Python class of the action distribution. + + Returns: + A list with length of ACTION_SPACE of float32 tensors of shapes + [T, B], ..., [T, B] corresponding to the sampling log probability + of the chosen action w.r.t. the policy. + """ + + log_probs = [] + if valid_action_trees is not None and False: + T = valid_action_trees.shape[0] + B = valid_action_trees.shape[1] + # If we have valid action trees here its because we are using gridnet and we cn calculate logp + # from actions in the tree to avoid huge amounts of unnecessary computation + for i in range(len(policy_logits)): + log_probs.append(torch.zeros([T, B])) + + grid_action_parts = len(model.grid_action_shape) + + for t in range(T): + for b in range(B): + x_tree = valid_action_trees[t,b] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + for a in range(grid_action_parts): + flattened_location = (model.width*y+x)*grid_action_parts+a + dist_part = policy_logits[flattened_location][t,b] + mask = torch.zeros([dist_part.shape[0]]) + mask[subtree_options] = 1 + + print(f'pre-mask logits: {dist_part}') + + logits = dist_part + torch.log(mask) + dist = Categorical(logits=logits) + action = actions[flattened_location][t,b] + print(f'mask: {mask}') + print(f'action: {action}') + print(f'post-mask logits: {logits}') + log_probs[flattened_location][t, b] = dist.log_prob(action) + + else: + for i in range(len(policy_logits)): + p_shape = policy_logits[i].shape + a_shape = actions[i].shape + policy_logits_flat = torch.reshape(policy_logits[i], + (-1,) + tuple(p_shape[2:])) + actions_flat = torch.reshape(actions[i], (-1,) + tuple(a_shape[2:])) + log_probs.append( + torch.reshape( + dist_class(policy_logits_flat, model).logp(actions_flat), + a_shape[:2])) + + return log_probs + + +def from_logits(behaviour_policy_logits, + target_policy_logits, + actions, + discounts, + rewards, + values, + bootstrap_value, + dist_class=TorchCategorical, + model=None, + clip_rho_threshold=1.0, + clip_pg_rho_threshold=1.0): + """multi_from_logits wrapper used only for tests""" + + res = multi_from_logits( + [behaviour_policy_logits], [target_policy_logits], [actions], + discounts, + rewards, + values, + bootstrap_value, + dist_class, + model, + clip_rho_threshold=clip_rho_threshold, + clip_pg_rho_threshold=clip_pg_rho_threshold) + + assert len(res.behaviour_action_log_probs) == 1 + assert len(res.target_action_log_probs) == 1 + return VTraceFromLogitsReturns( + vs=res.vs, + pg_advantages=res.pg_advantages, + log_rhos=res.log_rhos, + behaviour_action_log_probs=res.behaviour_action_log_probs[0], + target_action_log_probs=res.target_action_log_probs[0], + ) + + +def multi_from_logits(valid_action_trees, + behaviour_policy_logits, + target_policy_logits, + actions, + discounts, + rewards, + values, + bootstrap_value, + dist_class, + model, + behaviour_action_log_probs=None, + clip_rho_threshold=1.0, + clip_pg_rho_threshold=1.0): + """V-trace for softmax policies. + + Calculates V-trace actor critic targets for softmax polices as described in + + "IMPALA: Scalable Distributed Deep-RL with + Importance Weighted Actor-Learner Architectures" + by Espeholt, Soyer, Munos et al. + + Target policy refers to the policy we are interested in improving and + behaviour policy refers to the policy that generated the given + rewards and actions. + + In the notation used throughout documentation and comments, T refers to the + time dimension ranging from 0 to T-1. B refers to the batch size and + ACTION_SPACE refers to the list of numbers each representing a number of + actions. + + Args: + behaviour_policy_logits: A list with length of ACTION_SPACE of float32 + tensors of shapes [T, B, ACTION_SPACE[0]], ..., + [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities + parameterizing the softmax behavior policy. + target_policy_logits: A list with length of ACTION_SPACE of float32 + tensors of shapes [T, B, ACTION_SPACE[0]], ..., + [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities + parameterizing the softmax target policy. + actions: A list with length of ACTION_SPACE of tensors of shapes + [T, B, ...], ..., [T, B, ...] + with actions sampled from the behavior policy. + discounts: A float32 tensor of shape [T, B] with the discount + encountered when following the behavior policy. + rewards: A float32 tensor of shape [T, B] with the rewards generated by + following the behavior policy. + values: A float32 tensor of shape [T, B] with the value function + estimates wrt. the target policy. + bootstrap_value: A float32 of shape [B] with the value function + estimate at time T. + dist_class: action distribution class for the logits. + model: backing ModelV2 instance + behaviour_action_log_probs: Precalculated values of the behavior + actions. + clip_rho_threshold: A scalar float32 tensor with the clipping threshold + for importance weights (rho) when calculating the baseline targets + (vs). rho^bar in the paper. + clip_pg_rho_threshold: A scalar float32 tensor with the clipping + threshold on rho_s in: + \rho_s \delta log \pi(a|x) (r + \gamma v_{s+1} - V(x_s)). + + Returns: + A `VTraceFromLogitsReturns` namedtuple with the following fields: + vs: A float32 tensor of shape [T, B]. Can be used as target to train a + baseline (V(x_t) - vs_t)^2. + pg_advantages: A float 32 tensor of shape [T, B]. Can be used as an + estimate of the advantage in the calculation of policy gradients. + log_rhos: A float32 tensor of shape [T, B] containing the log + importance sampling weights (log rhos). + behaviour_action_log_probs: A float32 tensor of shape [T, B] containing + behaviour policy action log probabilities (log \mu(a_t)). + target_action_log_probs: A float32 tensor of shape [T, B] containing + target policy action probabilities (log \pi(a_t)). + """ + + behaviour_policy_logits = convert_to_torch_tensor( + behaviour_policy_logits, device="cpu") + target_policy_logits = convert_to_torch_tensor( + target_policy_logits, device="cpu") + actions = convert_to_torch_tensor(actions, device="cpu") + + # Make sure tensor ranks are as expected. + # The rest will be checked by from_action_log_probs. + for i in range(len(behaviour_policy_logits)): + assert len(behaviour_policy_logits[i].size()) == 3 + assert len(target_policy_logits[i].size()) == 3 + + target_action_log_probs = multi_log_probs_from_logits_and_actions( + target_policy_logits, actions, dist_class, model, valid_action_trees) + + if (len(behaviour_policy_logits) > 1 + or behaviour_action_log_probs is None): + # can't use precalculated values, recompute them. Note that + # recomputing won't work well for autoregressive action dists + # which may have variables not captured by 'logits' + behaviour_action_log_probs = multi_log_probs_from_logits_and_actions( + behaviour_policy_logits, actions, dist_class, model, valid_action_trees) + + behaviour_action_log_probs = convert_to_torch_tensor( + behaviour_action_log_probs, device="cpu") + behaviour_action_log_probs = force_list(behaviour_action_log_probs) + log_rhos = get_log_rhos(target_action_log_probs, + behaviour_action_log_probs) + + vtrace_returns = from_importance_weights( + log_rhos=log_rhos, + discounts=discounts, + rewards=rewards, + values=values, + bootstrap_value=bootstrap_value, + clip_rho_threshold=clip_rho_threshold, + clip_pg_rho_threshold=clip_pg_rho_threshold) + + return VTraceFromLogitsReturns( + log_rhos=log_rhos, + behaviour_action_log_probs=behaviour_action_log_probs, + target_action_log_probs=target_action_log_probs, + **vtrace_returns._asdict()) + + +def from_importance_weights(log_rhos, + discounts, + rewards, + values, + bootstrap_value, + clip_rho_threshold=1.0, + clip_pg_rho_threshold=1.0): + """V-trace from log importance weights. + + Calculates V-trace actor critic targets as described in + + "IMPALA: Scalable Distributed Deep-RL with + Importance Weighted Actor-Learner Architectures" + by Espeholt, Soyer, Munos et al. + + In the notation used throughout documentation and comments, T refers to the + time dimension ranging from 0 to T-1. B refers to the batch size. This code + also supports the case where all tensors have the same number of additional + dimensions, e.g., `rewards` is [T, B, C], `values` is [T, B, C], + `bootstrap_value` is [B, C]. + + Args: + log_rhos: A float32 tensor of shape [T, B] representing the log + importance sampling weights, i.e. + log(target_policy(a) / behaviour_policy(a)). V-trace performs + operations on rhos in log-space for numerical stability. + discounts: A float32 tensor of shape [T, B] with discounts encountered + when following the behaviour policy. + rewards: A float32 tensor of shape [T, B] containing rewards generated + by following the behaviour policy. + values: A float32 tensor of shape [T, B] with the value function + estimates wrt. the target policy. + bootstrap_value: A float32 of shape [B] with the value function + estimate at time T. + clip_rho_threshold: A scalar float32 tensor with the clipping threshold + for importance weights (rho) when calculating the baseline targets + (vs). rho^bar in the paper. If None, no clipping is applied. + clip_pg_rho_threshold: A scalar float32 tensor with the clipping + threshold on rho_s in + \rho_s \delta log \pi(a|x) (r + \gamma v_{s+1} - V(x_s)). + If None, no clipping is applied. + + Returns: + A VTraceReturns namedtuple (vs, pg_advantages) where: + vs: A float32 tensor of shape [T, B]. Can be used as target to + train a baseline (V(x_t) - vs_t)^2. + pg_advantages: A float32 tensor of shape [T, B]. Can be used as the + advantage in the calculation of policy gradients. + """ + log_rhos = convert_to_torch_tensor(log_rhos, device="cpu") + discounts = convert_to_torch_tensor(discounts, device="cpu") + rewards = convert_to_torch_tensor(rewards, device="cpu") + values = convert_to_torch_tensor(values, device="cpu") + bootstrap_value = convert_to_torch_tensor(bootstrap_value, device="cpu") + + # Make sure tensor ranks are consistent. + rho_rank = len(log_rhos.size()) # Usually 2. + assert rho_rank == len(values.size()) + assert rho_rank - 1 == len(bootstrap_value.size()), \ + "must have rank {}".format(rho_rank - 1) + assert rho_rank == len(discounts.size()) + assert rho_rank == len(rewards.size()) + + rhos = torch.exp(log_rhos) + if clip_rho_threshold is not None: + clipped_rhos = torch.clamp_max(rhos, clip_rho_threshold) + else: + clipped_rhos = rhos + + cs = torch.clamp_max(rhos, 1.0) + # Append bootstrapped value to get [v1, ..., v_t+1] + values_t_plus_1 = torch.cat( + [values[1:], torch.unsqueeze(bootstrap_value, 0)], dim=0) + deltas = clipped_rhos * (rewards + discounts * values_t_plus_1 - values) + + vs_minus_v_xs = [torch.zeros_like(bootstrap_value)] + for i in reversed(range(len(discounts))): + discount_t, c_t, delta_t = discounts[i], cs[i], deltas[i] + vs_minus_v_xs.append(delta_t + discount_t * c_t * vs_minus_v_xs[-1]) + vs_minus_v_xs = torch.stack(vs_minus_v_xs[1:]) + # Reverse the results back to original order. + vs_minus_v_xs = torch.flip(vs_minus_v_xs, dims=[0]) + # Add V(x_s) to get v_s. + vs = vs_minus_v_xs + values + + # Advantage for policy gradient. + vs_t_plus_1 = torch.cat( + [vs[1:], torch.unsqueeze(bootstrap_value, 0)], dim=0) + if clip_pg_rho_threshold is not None: + clipped_pg_rhos = torch.clamp_max(rhos, clip_pg_rho_threshold) + else: + clipped_pg_rhos = rhos + pg_advantages = ( + clipped_pg_rhos * (rewards + discounts * vs_t_plus_1 - values)) + + # Make sure no gradients backpropagated through the returned values. + return VTraceReturns(vs=vs.detach(), pg_advantages=pg_advantages.detach()) + + +def get_log_rhos(target_action_log_probs, behaviour_action_log_probs): + """With the selected log_probs for multi-discrete actions of behavior + and target policies we compute the log_rhos for calculating the vtrace.""" + t = torch.stack(target_action_log_probs) + b = torch.stack(behaviour_action_log_probs) + log_rhos = torch.sum(t - b, dim=0) + return log_rhos diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py index 20dcb7ea3..16328ba39 100644 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py @@ -1,9 +1,10 @@ +import functools import logging import gym import numpy as np import ray -from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major, VTraceLoss +from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major from ray.rllib.models.torch.torch_action_dist import TorchCategorical from ray.rllib.policy.sample_batch import SampleBatch from ray.rllib.policy.torch_policy import LearningRateSchedule, \ @@ -11,13 +12,131 @@ from ray.rllib.utils.framework import try_import_torch from ray.rllib.utils.torch_ops import sequence_mask +import griddly.util.rllib.torch.impala.im_vtrace_torch as im_vtrace from griddly.util.rllib.torch.mixins.invalid_action_policy_masking import InvalidActionMaskingPolicyMixin +from griddly.util.rllib.torch.torch_gridnet_masked_categorical_distribution import GridnetMaskedCategoricalDistribution torch, nn = try_import_torch() logger = logging.getLogger(__name__) +class VTraceLoss: + def __init__(self, + valid_action_trees, + actions, + actions_logp, + actions_entropy, + dones, + behaviour_action_logp, + behaviour_logits, + target_logits, + discount, + rewards, + values, + bootstrap_value, + dist_class, + model, + valid_mask, + config, + vf_loss_coeff=0.5, + entropy_coeff=0.01, + clip_rho_threshold=1.0, + clip_pg_rho_threshold=1.0): + """Policy gradient loss with vtrace importance weighting. + + VTraceLoss takes tensors of shape [T, B, ...], where `B` is the + batch_size. The reason we need to know `B` is for V-trace to properly + handle episode cut boundaries. + + Args: + actions: An int|float32 tensor of shape [T, B, ACTION_SPACE]. + actions_logp: A float32 tensor of shape [T, B]. + actions_entropy: A float32 tensor of shape [T, B]. + dones: A bool tensor of shape [T, B]. + behaviour_action_logp: Tensor of shape [T, B]. + behaviour_logits: A list with length of ACTION_SPACE of float32 + tensors of shapes + [T, B, ACTION_SPACE[0]], + ..., + [T, B, ACTION_SPACE[-1]] + target_logits: A list with length of ACTION_SPACE of float32 + tensors of shapes + [T, B, ACTION_SPACE[0]], + ..., + [T, B, ACTION_SPACE[-1]] + discount: A float32 scalar. + rewards: A float32 tensor of shape [T, B]. + values: A float32 tensor of shape [T, B]. + bootstrap_value: A float32 tensor of shape [B]. + dist_class: action distribution class for logits. + valid_mask: A bool tensor of valid RNN input elements (#2992). + config: Trainer config dict. + """ + + if valid_mask is None: + valid_mask = torch.ones_like(actions_logp) + + # Compute vtrace on the CPU for better perf + # (devices handled inside `vtrace.multi_from_logits`). + device = behaviour_action_logp[0].device + self.vtrace_returns = im_vtrace.multi_from_logits( + valid_action_trees=valid_action_trees, + behaviour_action_log_probs=behaviour_action_logp, + behaviour_policy_logits=behaviour_logits, + target_policy_logits=target_logits, + actions=torch.unbind(actions, dim=2), + discounts=(1.0 - dones.float()) * discount, + rewards=rewards, + values=values, + bootstrap_value=bootstrap_value, + dist_class=dist_class, + model=model, + clip_rho_threshold=clip_rho_threshold, + clip_pg_rho_threshold=clip_pg_rho_threshold) + # Move v-trace results back to GPU for actual loss computing. + self.value_targets = self.vtrace_returns.vs.to(device) + + # The policy gradients loss. + self.pi_loss = -torch.sum( + actions_logp * self.vtrace_returns.pg_advantages.to(device) * + valid_mask) + + # The baseline loss. + delta = (values - self.value_targets) * valid_mask + self.vf_loss = 0.5 * torch.sum(torch.pow(delta, 2.0)) + + # The entropy loss. + self.entropy = torch.sum(actions_entropy * valid_mask) + + # The summed weighted loss. + self.total_loss = (self.pi_loss + self.vf_loss * vf_loss_coeff - + self.entropy * entropy_coeff) + + +def make_time_major_valid_action_trees(policy, seq_lens, valid_action_trees, drop_last=False): + """ + Swaps batch and trajectory axis. + """ + + if policy.is_recurrent(): + raise NotImplementedError("Recurrent neural networks not supported with tree masking") + else: + # Important: chop the tensor into batches at known episode cut + # boundaries. + T = policy.config["rollout_fragment_length"] + B = len(valid_action_trees) // T + + rs = np.array(np.split(np.array(valid_action_trees), (4))) + + # Swap B and T axes. + res = np.transpose(rs) + + if drop_last: + return res[:-1] + return res + + def build_invalid_masking_vtrace_loss(policy, model, dist_class, train_batch): model_out, _ = model.from_batch(train_batch) @@ -42,6 +161,7 @@ def _make_time_major(*args, **kw): behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS] valid_action_mask = torch.tensor(train_batch['valid_action_mask']) + valid_action_trees = train_batch['valid_action_trees'] if 'seq_lens' in train_batch: max_seq_len = policy.config['rollout_fragment_length'] @@ -50,7 +170,10 @@ def _make_time_major(*args, **kw): else: mask = torch.ones_like(rewards) - # valid actions masks should be all ones where values are masked out to avoid nan party. + is_gridnet = hasattr(model, 'grid_channels') + if is_gridnet: + dist_class = functools.partial(GridnetMaskedCategoricalDistribution, valid_action_trees=valid_action_trees) + valid_action_mask[torch.where(mask == False)] = 1 model_out += torch.log(valid_action_mask) @@ -73,6 +196,9 @@ def _make_time_major(*args, **kw): # Inputs are reshaped from [B * T] => [T - 1, B] for V-trace calc. policy.loss = VTraceLoss( + valid_action_trees=make_time_major_valid_action_trees( + policy, train_batch.get("seq_lens"), valid_action_trees, drop_last=True) + if is_gridnet else None, actions=_make_time_major(loss_actions, drop_last=True), actions_logp=_make_time_major( action_dist.logp(actions), drop_last=True), @@ -108,6 +234,9 @@ def setup_mixins(policy, obs_space, action_space, config): def postprocess_episode(policy, sample_batch, other_agent_batches, episode): + if 'valid_action_trees' not in sample_batch: + sample_batch['valid_action_trees'] = [{} for _ in range(sample_batch['action_dist_inputs'].shape[0])] + if 'valid_action_mask' not in sample_batch: sample_batch['valid_action_mask'] = np.ones_like(sample_batch['action_dist_inputs']) else: diff --git a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py index 979d2904c..f8953415c 100644 --- a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py +++ b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py @@ -12,7 +12,7 @@ import torch from griddly.util.rllib.torch.conditional_masking_exploration import TorchConditionalMaskingExploration -from griddly.util.rllib.torch.conditional_masking_gridnet_exploration import TorchConditionalMaskingGridnetExploration +from griddly.util.rllib.torch.torch_gridnet_masked_categorical_distribution import GridnetMaskedCategoricalDistribution class InvalidActionMaskingPolicyMixin: @@ -73,12 +73,26 @@ def compute_actions( valid_action_trees.append({0: {0: {0: [0]}}}) if hasattr(self.model, 'grid_channels'): - exploration = TorchConditionalMaskingGridnetExploration( - self.model, - self.dist_class, - dist_inputs, - valid_action_trees, - ) + + self.dist_class = functools.partial(GridnetMaskedCategoricalDistribution, valid_action_trees=valid_action_trees) + action_dist = self.dist_class(dist_inputs, self.model) + + # Get the exploration action from the forward results. + actions, logp = \ + self.exploration.get_exploration_action( + action_distribution=action_dist, + timestep=timestep, + explore=explore) + + masked_logits = action_dist.sampled_masked_logits() + mask = action_dist.sampled_action_masks() + + # exploration = TorchConditionalMaskingGridnetExploration( + # self.model, + # self.dist_class, + # dist_inputs, + # valid_action_trees, + # ) else: exploration = TorchConditionalMaskingExploration( self.model, @@ -87,12 +101,13 @@ def compute_actions( valid_action_trees, ) - actions, masked_logits, logp, mask = exploration.get_actions_and_mask() + actions, masked_logits, logp, mask = exploration.get_actions_and_mask() input_dict[SampleBatch.ACTIONS] = actions extra_fetches = { 'valid_action_mask': mask, + 'valid_action_trees': valid_action_trees, } # Action-dist inputs. diff --git a/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py b/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py new file mode 100644 index 000000000..18ad5cbca --- /dev/null +++ b/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py @@ -0,0 +1,176 @@ +from ray.rllib.models import ActionDistribution +from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper + +import torch +from torch.distributions import Categorical + + +class GridnetMaskedCategoricalDistribution(TorchDistributionWrapper): + + def __init__(self, dist_inputs, model, valid_action_trees): + self._valid_action_trees = valid_action_trees + + self._num_inputs = dist_inputs.shape[0] + + self._width = model.width + self._height = model.height + self._grid_action_shape = model.grid_action_shape + self._grid_action_parts = len(self._grid_action_shape) + self._grid_channels = model.grid_channels + self._dist_inputs_reshaped = dist_inputs.reshape(-1, self._grid_channels, self._width, self._height) + + def logp(self, actions): + + logp_sums = torch.zeros([self._num_inputs]) + grid_actions = actions.reshape(-1, self._grid_action_parts, self._width, self._height) + + # run through the trees and only calculate where valid actions exist + for i in range(self._num_inputs): + x_tree = self._valid_action_trees[i] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + dist_input = self._dist_inputs_reshaped[i, :, x, y] + dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) + + logp_parts = torch.zeros([self._grid_action_parts]) + for a in range(self._grid_action_parts): + dist_part = dist_input_split[a] + mask = torch.zeros([dist_part.shape[0]]) + mask[subtree_options] = 1 + + logits = dist_part + torch.log(mask) + dist = Categorical(logits=logits) + logp_parts[a] = dist.log_prob(grid_actions[i, a, x, y]) + + logp_sums[i] += torch.sum(logp_parts) + + return logp_sums + + def entropy(self): + + entropy_sums = torch.zeros([self._num_inputs]) + + # Entropy for everything by the valid action locations will be 0 + # as the masks only allow selection of a single action + for i in range(self._num_inputs): + x_tree = self._valid_action_trees[i] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + dist_input = self._dist_inputs_reshaped[i, :, x, y] + dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) + + entropy_parts = torch.zeros([self._grid_action_parts]) + for a in range(self._grid_action_parts): + dist_part = dist_input_split[a] + mask = torch.zeros([dist_part.shape[0]]) + mask[subtree_options] = 1 + + logits = dist_part + torch.log(mask) + dist = Categorical(logits=logits) + entropy_parts[a] = dist.entropy() + + entropy_sums[i] += torch.sum(entropy_parts) + + return entropy_sums + + def kl(self, other: ActionDistribution): + pass + + def _mask_and_sample(self, options, logits): + + mask = torch.zeros([logits.shape[0]]) + mask[options] = 1 + + logits += torch.log(mask) + dist = Categorical(logits=logits) + sampled = dist.sample() + logp = dist.log_prob(sampled) + + return sampled, logp, logits, mask + + def sample(self): + self._last_sample = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) + self._last_sample_masked_logits = torch.zeros( + [self._num_inputs, self._grid_channels, self._width, self._height]) + self._last_sample_masks = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) + self._last_sample_logp = torch.zeros([self._num_inputs]) + + # Initialize the masks to NOP + self._last_sample_masks[:, 0, :, :] = 1 + for action_logit_size in self._grid_action_shape[:-1]: + self._last_sample_masks[:, action_logit_size, :, :] = 0 + + for i in range(self._num_inputs): + if len(self._valid_action_trees) >= 1: + + x_tree = self._valid_action_trees[i] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + + # Only bother with calculating actions for things that are possible in the grid + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + dist_input = self._dist_inputs_reshaped[i, :, x, y] + dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) + + logp_parts = torch.zeros([self._grid_action_parts]) + mask_offset = 0 + for a in range(self._grid_action_parts): + dist_part = dist_input_split[a] + + sampled, logp, masked_part_logits, mask_part = self._mask_and_sample( + subtree_options, + dist_part + ) + + # Set the action and the mask for each part of the action + logit_end = mask_offset + self._grid_action_shape[a] + self._last_sample[i, a, x, y] = sampled + self._last_sample_masked_logits[i, mask_offset:logit_end, x, y] = masked_part_logits + self._last_sample_masks[i, mask_offset:logit_end, x, y] = mask_part + + logp_parts[a] = logp + + if mask_part.sum() == 0: + raise RuntimeError('mask calculated incorrectly') + + mask_offset += self._grid_action_shape[a] + + if isinstance(subtree, dict): + subtree = subtree[int(sampled)] + if isinstance(subtree, dict): + subtree_options = list(subtree.keys()) + else: + # Leaf nodes with action_id list + subtree_options = subtree + self._last_sample_logp[i] += torch.sum(logp_parts) + + return self._last_sample.flatten(1) + + def sampled_action_masks(self): + return self._last_sample_masks.flatten(1) + + def sampled_masked_logits(self): + return self._last_sample_masked_logits.flatten(1) + + def sampled_action_logp(self): + return self._last_sample_logp From 7f59ab2dc81b9d943eb1903c1eb5439abd3fe07d Mon Sep 17 00:00:00 2001 From: Bam4d Date: Thu, 4 Mar 2021 12:30:38 +0000 Subject: [PATCH 23/34] giving up on gridnet+IMPALA. Far too many things not working in rllib --- python/examples/rllib/GriddlyRTS_test.yaml | 3 + python/examples/rllib/requirements.txt | 2 +- python/examples/rllib/rllib_RTS_Gridnet.py | 63 --- .../util/rllib/torch/agents/gridnet_agent.py | 68 --- .../rllib/torch/impala/im_vtrace_torch.py | 390 ------------------ .../torch/impala/im_vtrace_torch_policy.py | 132 +----- .../mixins/invalid_action_policy_masking.py | 102 ++++- ...gridnet_masked_categorical_distribution.py | 69 +++- python/griddly/util/rllib/wrappers/gridnet.py | 6 +- 9 files changed, 163 insertions(+), 672 deletions(-) delete mode 100644 python/examples/rllib/rllib_RTS_Gridnet.py delete mode 100644 python/griddly/util/rllib/torch/agents/gridnet_agent.py delete mode 100644 python/griddly/util/rllib/torch/impala/im_vtrace_torch.py diff --git a/python/examples/rllib/GriddlyRTS_test.yaml b/python/examples/rllib/GriddlyRTS_test.yaml index fe73f813d..0c1f72e2c 100644 --- a/python/examples/rllib/GriddlyRTS_test.yaml +++ b/python/examples/rllib/GriddlyRTS_test.yaml @@ -24,6 +24,9 @@ Environment: Lose: - eq: [ base:count, 0 ] # If the player has no bases Levels: + - | + H2 . + . H1 - | W W W W W W W W W W W W W B2 . H2 . . . . . . . W diff --git a/python/examples/rllib/requirements.txt b/python/examples/rllib/requirements.txt index ee2962b46..7e7d9d770 100644 --- a/python/examples/rllib/requirements.txt +++ b/python/examples/rllib/requirements.txt @@ -1,2 +1,2 @@ -ray[rllib] >= 1.1.0 +ray[rllib] >= 1.2.0 torch >= 1.7.1 \ No newline at end of file diff --git a/python/examples/rllib/rllib_RTS_Gridnet.py b/python/examples/rllib/rllib_RTS_Gridnet.py deleted file mode 100644 index 49bbb3b29..000000000 --- a/python/examples/rllib/rllib_RTS_Gridnet.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import sys - -import ray -from ray import tune -from ray.rllib.models import ModelCatalog -from ray.tune.registry import register_env - -from griddly import gd -from griddly.util.rllib.torch.agents.gridnet_agent import GridnetAgent -from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer -from griddly.util.rllib.wrappers.core import RLlibMultiAgentWrapper -from griddly.util.rllib.wrappers.gridnet import RLlibGridnetEnv - - -if __name__ == '__main__': - sep = os.pathsep - os.environ['PYTHONPATH'] = sep.join(sys.path) - - ray.init(num_gpus=1, local_mode=True) - - env_name = 'ray-griddly-rts-env' - - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') - - # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play - def _env_create(env_config): - env = RLlibGridnetEnv(env_config) - return RLlibMultiAgentWrapper(env, env_config) - - register_env(env_name, _env_create) - ModelCatalog.register_custom_model('GridnetAgent', GridnetAgent) - - config = { - 'framework': 'torch', - 'num_workers': 1, - 'num_envs_per_worker': 1, - - "_use_trajectory_view_api": False, - 'model': { - 'custom_model': 'GridnetAgent', - 'custom_model_config': {} - }, - 'env': env_name, - 'env_config': { - 'invalid_action_masking': True, - # 'record_video_config': { - # 'frequency': 100000 # number of rollouts - # }, - - 'yaml_file': test_path, - 'global_observer_type': gd.ObserverType.ISOMETRIC, - 'level': 1, - 'max_steps': 1000, - }, - #'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) - } - - stop = { - 'timesteps_total': 200000000, - } - - result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/griddly/util/rllib/torch/agents/gridnet_agent.py b/python/griddly/util/rllib/torch/agents/gridnet_agent.py deleted file mode 100644 index 89c60b623..000000000 --- a/python/griddly/util/rllib/torch/agents/gridnet_agent.py +++ /dev/null @@ -1,68 +0,0 @@ -from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 -from torch import nn -import numpy as np - -from griddly.util.rllib.torch.agents.global_average_pooling_agent import layer_init, GlobalAvePool - - -class GridnetAgent(TorchModelV2, nn.Module): - def __init__(self, obs_space, action_space, num_outputs, model_config, name): - super().__init__(obs_space, action_space, num_outputs, model_config, name) - nn.Module.__init__(self) - - self.height = obs_space.shape[0] - self.width = obs_space.shape[1] - self.observation_channels = obs_space.shape[2] - - self.grid_action_shape = action_space.nvec[:int(action_space.shape[0] / (self.height * self.height))] - self.grid_channels = np.sum(self.grid_action_shape) - - self._encoder = nn.Sequential( - nn.Conv2d(self.observation_channels, 32, kernel_size=3, padding=1), - nn.MaxPool2d(3, stride=1, padding=1), - nn.ReLU(), - nn.Conv2d(32, 64, kernel_size=3, padding=1), - nn.MaxPool2d(3, stride=1, padding=1), - nn.ReLU(), - # nn.Conv2d(64, 128, kernel_size=3, padding=1), - # nn.MaxPool2d(3, stride=1, padding=1), - # nn.ReLU(), - # nn.Conv2d(128, 256, kernel_size=3, padding=1), - # nn.MaxPool2d(3, stride=1, padding=1), - ) - - self._decode = nn.Sequential( - # nn.ConvTranspose2d(256, 128, 3, stride=1, padding=1), - # nn.ReLU(), - # nn.ConvTranspose2d(128, 64, 3, stride=1, padding=1), - # nn.ReLU(), - nn.ConvTranspose2d(64, 32, 3, stride=1, padding=1), - nn.ReLU(), - nn.ConvTranspose2d(32, self.grid_channels, 3, stride=1, padding=1), - nn.ReLU(), - nn.Flatten(), - ) - - self.critic = nn.Sequential( - GlobalAvePool(256), - nn.Flatten(), - layer_init(nn.Linear(256, 256)), - nn.ReLU(), - layer_init(nn.Linear(256, 1), std=1) - ) - - def forward(self, input_dict, state, seq_lens): - input_obs = input_dict['obs'].permute(0, 3, 1, 2) - self._encoded = self._encoder(input_obs) - - # Value function - value = self.critic(self._encoded) - self._value = value.reshape(-1) - - # Logits for actions - logits = self._decode(self._encoded) - - return logits, state - - def value_function(self): - return self._value diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py deleted file mode 100644 index 4bf855db7..000000000 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""PyTorch version of the functions to compute V-trace off-policy actor critic -targets. - -For details and theory see: - -"IMPALA: Scalable Distributed Deep-RL with -Importance Weighted Actor-Learner Architectures" -by Espeholt, Soyer, Munos et al. - -See https://arxiv.org/abs/1802.01561 for the full paper. - -In addition to the original paper's code, changes have been made -to support MultiDiscrete action spaces. behaviour_policy_logits, -target_policy_logits and actions parameters in the entry point -multi_from_logits method accepts lists of tensors instead of just -tensors. -""" - -from ray.rllib.agents.impala.vtrace_tf import VTraceFromLogitsReturns, \ - VTraceReturns -from ray.rllib.models.torch.torch_action_dist import TorchCategorical -from ray.rllib.utils import force_list -from ray.rllib.utils.framework import try_import_torch -from ray.rllib.utils.torch_ops import convert_to_torch_tensor -from torch.distributions import Categorical - -torch, nn = try_import_torch() - - -def log_probs_from_logits_and_actions(policy_logits, - actions, - dist_class=TorchCategorical, - model=None): - return multi_log_probs_from_logits_and_actions([policy_logits], [actions], - dist_class, model)[0] - - -def multi_log_probs_from_logits_and_actions(policy_logits, actions, dist_class, - model, valid_action_trees=None): - """Computes action log-probs from policy logits and actions. - - In the notation used throughout documentation and comments, T refers to the - time dimension ranging from 0 to T-1. B refers to the batch size and - ACTION_SPACE refers to the list of numbers each representing a number of - actions. - - Args: - policy_logits: A list with length of ACTION_SPACE of float32 - tensors of shapes [T, B, ACTION_SPACE[0]], ..., - [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities - parameterizing a softmax policy. - actions: A list with length of ACTION_SPACE of tensors of shapes - [T, B, ...], ..., [T, B, ...] - with actions. - dist_class: Python class of the action distribution. - - Returns: - A list with length of ACTION_SPACE of float32 tensors of shapes - [T, B], ..., [T, B] corresponding to the sampling log probability - of the chosen action w.r.t. the policy. - """ - - log_probs = [] - if valid_action_trees is not None and False: - T = valid_action_trees.shape[0] - B = valid_action_trees.shape[1] - # If we have valid action trees here its because we are using gridnet and we cn calculate logp - # from actions in the tree to avoid huge amounts of unnecessary computation - for i in range(len(policy_logits)): - log_probs.append(torch.zeros([T, B])) - - grid_action_parts = len(model.grid_action_shape) - - for t in range(T): - for b in range(B): - x_tree = valid_action_trees[t,b] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - for a in range(grid_action_parts): - flattened_location = (model.width*y+x)*grid_action_parts+a - dist_part = policy_logits[flattened_location][t,b] - mask = torch.zeros([dist_part.shape[0]]) - mask[subtree_options] = 1 - - print(f'pre-mask logits: {dist_part}') - - logits = dist_part + torch.log(mask) - dist = Categorical(logits=logits) - action = actions[flattened_location][t,b] - print(f'mask: {mask}') - print(f'action: {action}') - print(f'post-mask logits: {logits}') - log_probs[flattened_location][t, b] = dist.log_prob(action) - - else: - for i in range(len(policy_logits)): - p_shape = policy_logits[i].shape - a_shape = actions[i].shape - policy_logits_flat = torch.reshape(policy_logits[i], - (-1,) + tuple(p_shape[2:])) - actions_flat = torch.reshape(actions[i], (-1,) + tuple(a_shape[2:])) - log_probs.append( - torch.reshape( - dist_class(policy_logits_flat, model).logp(actions_flat), - a_shape[:2])) - - return log_probs - - -def from_logits(behaviour_policy_logits, - target_policy_logits, - actions, - discounts, - rewards, - values, - bootstrap_value, - dist_class=TorchCategorical, - model=None, - clip_rho_threshold=1.0, - clip_pg_rho_threshold=1.0): - """multi_from_logits wrapper used only for tests""" - - res = multi_from_logits( - [behaviour_policy_logits], [target_policy_logits], [actions], - discounts, - rewards, - values, - bootstrap_value, - dist_class, - model, - clip_rho_threshold=clip_rho_threshold, - clip_pg_rho_threshold=clip_pg_rho_threshold) - - assert len(res.behaviour_action_log_probs) == 1 - assert len(res.target_action_log_probs) == 1 - return VTraceFromLogitsReturns( - vs=res.vs, - pg_advantages=res.pg_advantages, - log_rhos=res.log_rhos, - behaviour_action_log_probs=res.behaviour_action_log_probs[0], - target_action_log_probs=res.target_action_log_probs[0], - ) - - -def multi_from_logits(valid_action_trees, - behaviour_policy_logits, - target_policy_logits, - actions, - discounts, - rewards, - values, - bootstrap_value, - dist_class, - model, - behaviour_action_log_probs=None, - clip_rho_threshold=1.0, - clip_pg_rho_threshold=1.0): - """V-trace for softmax policies. - - Calculates V-trace actor critic targets for softmax polices as described in - - "IMPALA: Scalable Distributed Deep-RL with - Importance Weighted Actor-Learner Architectures" - by Espeholt, Soyer, Munos et al. - - Target policy refers to the policy we are interested in improving and - behaviour policy refers to the policy that generated the given - rewards and actions. - - In the notation used throughout documentation and comments, T refers to the - time dimension ranging from 0 to T-1. B refers to the batch size and - ACTION_SPACE refers to the list of numbers each representing a number of - actions. - - Args: - behaviour_policy_logits: A list with length of ACTION_SPACE of float32 - tensors of shapes [T, B, ACTION_SPACE[0]], ..., - [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities - parameterizing the softmax behavior policy. - target_policy_logits: A list with length of ACTION_SPACE of float32 - tensors of shapes [T, B, ACTION_SPACE[0]], ..., - [T, B, ACTION_SPACE[-1]] with un-normalized log-probabilities - parameterizing the softmax target policy. - actions: A list with length of ACTION_SPACE of tensors of shapes - [T, B, ...], ..., [T, B, ...] - with actions sampled from the behavior policy. - discounts: A float32 tensor of shape [T, B] with the discount - encountered when following the behavior policy. - rewards: A float32 tensor of shape [T, B] with the rewards generated by - following the behavior policy. - values: A float32 tensor of shape [T, B] with the value function - estimates wrt. the target policy. - bootstrap_value: A float32 of shape [B] with the value function - estimate at time T. - dist_class: action distribution class for the logits. - model: backing ModelV2 instance - behaviour_action_log_probs: Precalculated values of the behavior - actions. - clip_rho_threshold: A scalar float32 tensor with the clipping threshold - for importance weights (rho) when calculating the baseline targets - (vs). rho^bar in the paper. - clip_pg_rho_threshold: A scalar float32 tensor with the clipping - threshold on rho_s in: - \rho_s \delta log \pi(a|x) (r + \gamma v_{s+1} - V(x_s)). - - Returns: - A `VTraceFromLogitsReturns` namedtuple with the following fields: - vs: A float32 tensor of shape [T, B]. Can be used as target to train a - baseline (V(x_t) - vs_t)^2. - pg_advantages: A float 32 tensor of shape [T, B]. Can be used as an - estimate of the advantage in the calculation of policy gradients. - log_rhos: A float32 tensor of shape [T, B] containing the log - importance sampling weights (log rhos). - behaviour_action_log_probs: A float32 tensor of shape [T, B] containing - behaviour policy action log probabilities (log \mu(a_t)). - target_action_log_probs: A float32 tensor of shape [T, B] containing - target policy action probabilities (log \pi(a_t)). - """ - - behaviour_policy_logits = convert_to_torch_tensor( - behaviour_policy_logits, device="cpu") - target_policy_logits = convert_to_torch_tensor( - target_policy_logits, device="cpu") - actions = convert_to_torch_tensor(actions, device="cpu") - - # Make sure tensor ranks are as expected. - # The rest will be checked by from_action_log_probs. - for i in range(len(behaviour_policy_logits)): - assert len(behaviour_policy_logits[i].size()) == 3 - assert len(target_policy_logits[i].size()) == 3 - - target_action_log_probs = multi_log_probs_from_logits_and_actions( - target_policy_logits, actions, dist_class, model, valid_action_trees) - - if (len(behaviour_policy_logits) > 1 - or behaviour_action_log_probs is None): - # can't use precalculated values, recompute them. Note that - # recomputing won't work well for autoregressive action dists - # which may have variables not captured by 'logits' - behaviour_action_log_probs = multi_log_probs_from_logits_and_actions( - behaviour_policy_logits, actions, dist_class, model, valid_action_trees) - - behaviour_action_log_probs = convert_to_torch_tensor( - behaviour_action_log_probs, device="cpu") - behaviour_action_log_probs = force_list(behaviour_action_log_probs) - log_rhos = get_log_rhos(target_action_log_probs, - behaviour_action_log_probs) - - vtrace_returns = from_importance_weights( - log_rhos=log_rhos, - discounts=discounts, - rewards=rewards, - values=values, - bootstrap_value=bootstrap_value, - clip_rho_threshold=clip_rho_threshold, - clip_pg_rho_threshold=clip_pg_rho_threshold) - - return VTraceFromLogitsReturns( - log_rhos=log_rhos, - behaviour_action_log_probs=behaviour_action_log_probs, - target_action_log_probs=target_action_log_probs, - **vtrace_returns._asdict()) - - -def from_importance_weights(log_rhos, - discounts, - rewards, - values, - bootstrap_value, - clip_rho_threshold=1.0, - clip_pg_rho_threshold=1.0): - """V-trace from log importance weights. - - Calculates V-trace actor critic targets as described in - - "IMPALA: Scalable Distributed Deep-RL with - Importance Weighted Actor-Learner Architectures" - by Espeholt, Soyer, Munos et al. - - In the notation used throughout documentation and comments, T refers to the - time dimension ranging from 0 to T-1. B refers to the batch size. This code - also supports the case where all tensors have the same number of additional - dimensions, e.g., `rewards` is [T, B, C], `values` is [T, B, C], - `bootstrap_value` is [B, C]. - - Args: - log_rhos: A float32 tensor of shape [T, B] representing the log - importance sampling weights, i.e. - log(target_policy(a) / behaviour_policy(a)). V-trace performs - operations on rhos in log-space for numerical stability. - discounts: A float32 tensor of shape [T, B] with discounts encountered - when following the behaviour policy. - rewards: A float32 tensor of shape [T, B] containing rewards generated - by following the behaviour policy. - values: A float32 tensor of shape [T, B] with the value function - estimates wrt. the target policy. - bootstrap_value: A float32 of shape [B] with the value function - estimate at time T. - clip_rho_threshold: A scalar float32 tensor with the clipping threshold - for importance weights (rho) when calculating the baseline targets - (vs). rho^bar in the paper. If None, no clipping is applied. - clip_pg_rho_threshold: A scalar float32 tensor with the clipping - threshold on rho_s in - \rho_s \delta log \pi(a|x) (r + \gamma v_{s+1} - V(x_s)). - If None, no clipping is applied. - - Returns: - A VTraceReturns namedtuple (vs, pg_advantages) where: - vs: A float32 tensor of shape [T, B]. Can be used as target to - train a baseline (V(x_t) - vs_t)^2. - pg_advantages: A float32 tensor of shape [T, B]. Can be used as the - advantage in the calculation of policy gradients. - """ - log_rhos = convert_to_torch_tensor(log_rhos, device="cpu") - discounts = convert_to_torch_tensor(discounts, device="cpu") - rewards = convert_to_torch_tensor(rewards, device="cpu") - values = convert_to_torch_tensor(values, device="cpu") - bootstrap_value = convert_to_torch_tensor(bootstrap_value, device="cpu") - - # Make sure tensor ranks are consistent. - rho_rank = len(log_rhos.size()) # Usually 2. - assert rho_rank == len(values.size()) - assert rho_rank - 1 == len(bootstrap_value.size()), \ - "must have rank {}".format(rho_rank - 1) - assert rho_rank == len(discounts.size()) - assert rho_rank == len(rewards.size()) - - rhos = torch.exp(log_rhos) - if clip_rho_threshold is not None: - clipped_rhos = torch.clamp_max(rhos, clip_rho_threshold) - else: - clipped_rhos = rhos - - cs = torch.clamp_max(rhos, 1.0) - # Append bootstrapped value to get [v1, ..., v_t+1] - values_t_plus_1 = torch.cat( - [values[1:], torch.unsqueeze(bootstrap_value, 0)], dim=0) - deltas = clipped_rhos * (rewards + discounts * values_t_plus_1 - values) - - vs_minus_v_xs = [torch.zeros_like(bootstrap_value)] - for i in reversed(range(len(discounts))): - discount_t, c_t, delta_t = discounts[i], cs[i], deltas[i] - vs_minus_v_xs.append(delta_t + discount_t * c_t * vs_minus_v_xs[-1]) - vs_minus_v_xs = torch.stack(vs_minus_v_xs[1:]) - # Reverse the results back to original order. - vs_minus_v_xs = torch.flip(vs_minus_v_xs, dims=[0]) - # Add V(x_s) to get v_s. - vs = vs_minus_v_xs + values - - # Advantage for policy gradient. - vs_t_plus_1 = torch.cat( - [vs[1:], torch.unsqueeze(bootstrap_value, 0)], dim=0) - if clip_pg_rho_threshold is not None: - clipped_pg_rhos = torch.clamp_max(rhos, clip_pg_rho_threshold) - else: - clipped_pg_rhos = rhos - pg_advantages = ( - clipped_pg_rhos * (rewards + discounts * vs_t_plus_1 - values)) - - # Make sure no gradients backpropagated through the returned values. - return VTraceReturns(vs=vs.detach(), pg_advantages=pg_advantages.detach()) - - -def get_log_rhos(target_action_log_probs, behaviour_action_log_probs): - """With the selected log_probs for multi-discrete actions of behavior - and target policies we compute the log_rhos for calculating the vtrace.""" - t = torch.stack(target_action_log_probs) - b = torch.stack(behaviour_action_log_probs) - log_rhos = torch.sum(t - b, dim=0) - return log_rhos diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py index 16328ba39..253ade08f 100644 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py +++ b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py @@ -4,7 +4,7 @@ import gym import numpy as np import ray -from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major +from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major, VTraceLoss from ray.rllib.models.torch.torch_action_dist import TorchCategorical from ray.rllib.policy.sample_batch import SampleBatch from ray.rllib.policy.torch_policy import LearningRateSchedule, \ @@ -12,7 +12,6 @@ from ray.rllib.utils.framework import try_import_torch from ray.rllib.utils.torch_ops import sequence_mask -import griddly.util.rllib.torch.impala.im_vtrace_torch as im_vtrace from griddly.util.rllib.torch.mixins.invalid_action_policy_masking import InvalidActionMaskingPolicyMixin from griddly.util.rllib.torch.torch_gridnet_masked_categorical_distribution import GridnetMaskedCategoricalDistribution @@ -20,123 +19,6 @@ logger = logging.getLogger(__name__) - -class VTraceLoss: - def __init__(self, - valid_action_trees, - actions, - actions_logp, - actions_entropy, - dones, - behaviour_action_logp, - behaviour_logits, - target_logits, - discount, - rewards, - values, - bootstrap_value, - dist_class, - model, - valid_mask, - config, - vf_loss_coeff=0.5, - entropy_coeff=0.01, - clip_rho_threshold=1.0, - clip_pg_rho_threshold=1.0): - """Policy gradient loss with vtrace importance weighting. - - VTraceLoss takes tensors of shape [T, B, ...], where `B` is the - batch_size. The reason we need to know `B` is for V-trace to properly - handle episode cut boundaries. - - Args: - actions: An int|float32 tensor of shape [T, B, ACTION_SPACE]. - actions_logp: A float32 tensor of shape [T, B]. - actions_entropy: A float32 tensor of shape [T, B]. - dones: A bool tensor of shape [T, B]. - behaviour_action_logp: Tensor of shape [T, B]. - behaviour_logits: A list with length of ACTION_SPACE of float32 - tensors of shapes - [T, B, ACTION_SPACE[0]], - ..., - [T, B, ACTION_SPACE[-1]] - target_logits: A list with length of ACTION_SPACE of float32 - tensors of shapes - [T, B, ACTION_SPACE[0]], - ..., - [T, B, ACTION_SPACE[-1]] - discount: A float32 scalar. - rewards: A float32 tensor of shape [T, B]. - values: A float32 tensor of shape [T, B]. - bootstrap_value: A float32 tensor of shape [B]. - dist_class: action distribution class for logits. - valid_mask: A bool tensor of valid RNN input elements (#2992). - config: Trainer config dict. - """ - - if valid_mask is None: - valid_mask = torch.ones_like(actions_logp) - - # Compute vtrace on the CPU for better perf - # (devices handled inside `vtrace.multi_from_logits`). - device = behaviour_action_logp[0].device - self.vtrace_returns = im_vtrace.multi_from_logits( - valid_action_trees=valid_action_trees, - behaviour_action_log_probs=behaviour_action_logp, - behaviour_policy_logits=behaviour_logits, - target_policy_logits=target_logits, - actions=torch.unbind(actions, dim=2), - discounts=(1.0 - dones.float()) * discount, - rewards=rewards, - values=values, - bootstrap_value=bootstrap_value, - dist_class=dist_class, - model=model, - clip_rho_threshold=clip_rho_threshold, - clip_pg_rho_threshold=clip_pg_rho_threshold) - # Move v-trace results back to GPU for actual loss computing. - self.value_targets = self.vtrace_returns.vs.to(device) - - # The policy gradients loss. - self.pi_loss = -torch.sum( - actions_logp * self.vtrace_returns.pg_advantages.to(device) * - valid_mask) - - # The baseline loss. - delta = (values - self.value_targets) * valid_mask - self.vf_loss = 0.5 * torch.sum(torch.pow(delta, 2.0)) - - # The entropy loss. - self.entropy = torch.sum(actions_entropy * valid_mask) - - # The summed weighted loss. - self.total_loss = (self.pi_loss + self.vf_loss * vf_loss_coeff - - self.entropy * entropy_coeff) - - -def make_time_major_valid_action_trees(policy, seq_lens, valid_action_trees, drop_last=False): - """ - Swaps batch and trajectory axis. - """ - - if policy.is_recurrent(): - raise NotImplementedError("Recurrent neural networks not supported with tree masking") - else: - # Important: chop the tensor into batches at known episode cut - # boundaries. - T = policy.config["rollout_fragment_length"] - B = len(valid_action_trees) // T - - rs = np.array(np.split(np.array(valid_action_trees), (4))) - - # Swap B and T axes. - res = np.transpose(rs) - - if drop_last: - return res[:-1] - return res - - def build_invalid_masking_vtrace_loss(policy, model, dist_class, train_batch): model_out, _ = model.from_batch(train_batch) @@ -160,8 +42,7 @@ def _make_time_major(*args, **kw): behaviour_action_logp = train_batch[SampleBatch.ACTION_LOGP] behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS] - valid_action_mask = torch.tensor(train_batch['valid_action_mask']) - valid_action_trees = train_batch['valid_action_trees'] + valid_action_mask = train_batch['valid_action_mask'] if 'seq_lens' in train_batch: max_seq_len = policy.config['rollout_fragment_length'] @@ -170,10 +51,6 @@ def _make_time_major(*args, **kw): else: mask = torch.ones_like(rewards) - is_gridnet = hasattr(model, 'grid_channels') - if is_gridnet: - dist_class = functools.partial(GridnetMaskedCategoricalDistribution, valid_action_trees=valid_action_trees) - valid_action_mask[torch.where(mask == False)] = 1 model_out += torch.log(valid_action_mask) @@ -196,9 +73,6 @@ def _make_time_major(*args, **kw): # Inputs are reshaped from [B * T] => [T - 1, B] for V-trace calc. policy.loss = VTraceLoss( - valid_action_trees=make_time_major_valid_action_trees( - policy, train_batch.get("seq_lens"), valid_action_trees, drop_last=True) - if is_gridnet else None, actions=_make_time_major(loss_actions, drop_last=True), actions_logp=_make_time_major( action_dist.logp(actions), drop_last=True), @@ -235,7 +109,7 @@ def setup_mixins(policy, obs_space, action_space, config): def postprocess_episode(policy, sample_batch, other_agent_batches, episode): if 'valid_action_trees' not in sample_batch: - sample_batch['valid_action_trees'] = [{} for _ in range(sample_batch['action_dist_inputs'].shape[0])] + sample_batch['valid_action_trees'] = np.array([{} for _ in range(sample_batch['action_dist_inputs'].shape[0])]) if 'valid_action_mask' not in sample_batch: sample_batch['valid_action_mask'] = np.ones_like(sample_batch['action_dist_inputs']) diff --git a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py index f8953415c..fef11b206 100644 --- a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py +++ b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py @@ -21,6 +21,95 @@ class InvalidActionMaskingPolicyMixin: info_batch, therefore we have to override it to explore/exploit valid actions. """ + @override(Policy) + def compute_actions_from_input_dict( + self, + input_dict: Dict[str, TensorType], + explore: bool = None, + timestep: Optional[int] = None, + episodes: Optional[List["MultiAgentEpisode"]] = None, + **kwargs) -> \ + Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: + + with torch.no_grad(): + # Pass lazy (torch) tensor dict to Model as `input_dict`. + input_dict = self._lazy_tensor_dict(input_dict) + # Pack internal state inputs into (separate) list. + state_batches = [ + input_dict[k] for k in input_dict.keys() if "state_in" in k[:8] + ] + # Calculate RNN sequence lengths. + seq_lens = np.array([1] * len(input_dict["obs"])) \ + if state_batches else None + + # Call the exploration before_compute_actions hook. + self.exploration.before_compute_actions( + explore=explore, timestep=timestep) + + dist_inputs, state_out = self.model(input_dict, state_batches, + seq_lens) + # Extract the tree from the info batch + valid_action_trees = [] + for info in input_dict[SampleBatch.INFOS]: + if isinstance(info, torch.Tensor): + valid_action_trees.append({0: {0: {0: [0]}}}) + elif 'valid_action_trees' in info: + valid_action_trees.append(info['valid_action_trees']) + + if hasattr(self.model, 'grid_channels'): + + self.dist_class = functools.partial(GridnetMaskedCategoricalDistribution, + valid_action_trees=valid_action_trees) + action_dist = self.dist_class(dist_inputs, self.model) + + # Get the exploration action from the forward results. + actions, logp = \ + self.exploration.get_exploration_action( + action_distribution=action_dist, + timestep=timestep, + explore=explore) + #if + masked_logits = action_dist.sampled_masked_logits() + mask = action_dist.sampled_action_masks() + + # exploration = TorchConditionalMaskingGridnetExploration( + # self.model, + # self.dist_class, + # dist_inputs, + # valid_action_trees, + # ) + else: + exploration = TorchConditionalMaskingExploration( + self.model, + self.dist_class, + dist_inputs, + valid_action_trees, + ) + + actions, masked_logits, logp, mask = exploration.get_actions_and_mask() + + input_dict[SampleBatch.ACTIONS] = actions + + extra_fetches = { + 'valid_action_mask': mask, + 'valid_action_trees': np.array(valid_action_trees), + } + + # Action-dist inputs. + if dist_inputs is not None: + extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_logits + + # Action-logp and action-prob. + if logp is not None: + extra_fetches[SampleBatch.ACTION_PROB] = \ + torch.exp(logp.float()) + extra_fetches[SampleBatch.ACTION_LOGP] = logp + + # Update our global timestep by the batch size. + self.global_timestep += len(input_dict[SampleBatch.CUR_OBS]) + + return convert_to_non_torch_type((actions, state_out, extra_fetches)) + @override(Policy) def compute_actions( self, @@ -67,8 +156,8 @@ def compute_actions( # Extract the tree from the info batch valid_action_trees = [] for info in info_batch: - if 'valid_action_tree' in info: - valid_action_trees.append(info['valid_action_tree']) + if 'valid_action_trees' in info: + valid_action_trees.append(info['valid_action_trees']) else: valid_action_trees.append({0: {0: {0: [0]}}}) @@ -84,8 +173,9 @@ def compute_actions( timestep=timestep, explore=explore) - masked_logits = action_dist.sampled_masked_logits() - mask = action_dist.sampled_action_masks() + if explore: + dist_inputs = action_dist.sampled_masked_logits() + mask = action_dist.sampled_action_masks() # exploration = TorchConditionalMaskingGridnetExploration( # self.model, @@ -107,12 +197,12 @@ def compute_actions( extra_fetches = { 'valid_action_mask': mask, - 'valid_action_trees': valid_action_trees, + 'valid_action_trees': np.array(valid_action_trees), } # Action-dist inputs. if dist_inputs is not None: - extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_logits + extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = dist_inputs # Action-logp and action-prob. if logp is not None: diff --git a/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py b/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py index 18ad5cbca..307cd4c0c 100644 --- a/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py +++ b/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py @@ -1,7 +1,8 @@ from ray.rllib.models import ActionDistribution from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper - +import numpy as np import torch +from ray.rllib.utils.typing import TensorType from torch.distributions import Categorical @@ -19,9 +20,62 @@ def __init__(self, dist_inputs, model, valid_action_trees): self._grid_channels = model.grid_channels self._dist_inputs_reshaped = dist_inputs.reshape(-1, self._grid_channels, self._width, self._height) + def _is_dummy(self): + # Hack for annoying rllib optimization where it tries to work out waht values you are using in your algorithm + return not isinstance(self._valid_action_trees[0], dict) + + def _reset(self): + self._last_sample = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) + self._last_sample_masks = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) + + self._last_sample_logp = torch.zeros([self._num_inputs]) + + # Initialize the masks to NOP + self._last_sample_masks[:, 0, :, :] = 1 + for action_logit_size in self._grid_action_shape[:-1]: + self._last_sample_masks[:, action_logit_size, :, :] = 1 + + self._last_sample_masked_logits = self._dist_inputs_reshaped + torch.log(self._last_sample_masks) + + def deterministic_sample(self): + + self._reset() + + for i in range(self._num_inputs): + x_tree = self._valid_action_trees[i] + + # In the case there are no available actions for the player + if len(x_tree) == 0: + continue + for x, y_tree in x_tree.items(): + for y, subtree in y_tree.items(): + + subtree_options = list(subtree.keys()) + + dist_input = self._dist_inputs_reshaped[i, :, x, y] + dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) + + entropy_parts = torch.zeros([self._grid_action_parts]) + for a in range(self._grid_action_parts): + dist_part = dist_input_split[a] + mask = torch.zeros([dist_part.shape[0]]) + mask[subtree_options] = 1 + + logits = dist_part + torch.log(mask) + dist = Categorical(logits=logits) + sampled = torch.argmax(dist.probs) + + self._last_sample[i, a, x, y] = sampled + + return self._last_sample + def logp(self, actions): logp_sums = torch.zeros([self._num_inputs]) + + if self._is_dummy(): + return logp_sums + grid_actions = actions.reshape(-1, self._grid_action_parts, self._width, self._height) # run through the trees and only calculate where valid actions exist @@ -57,6 +111,9 @@ def entropy(self): entropy_sums = torch.zeros([self._num_inputs]) + if self._is_dummy(): + return entropy_sums + # Entropy for everything by the valid action locations will be 0 # as the masks only allow selection of a single action for i in range(self._num_inputs): @@ -103,16 +160,8 @@ def _mask_and_sample(self, options, logits): return sampled, logp, logits, mask def sample(self): - self._last_sample = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) - self._last_sample_masked_logits = torch.zeros( - [self._num_inputs, self._grid_channels, self._width, self._height]) - self._last_sample_masks = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) - self._last_sample_logp = torch.zeros([self._num_inputs]) - # Initialize the masks to NOP - self._last_sample_masks[:, 0, :, :] = 1 - for action_logit_size in self._grid_action_shape[:-1]: - self._last_sample_masks[:, action_logit_size, :, :] = 0 + self._reset() for i in range(self._num_inputs): if len(self._valid_action_trees) >= 1: diff --git a/python/griddly/util/rllib/wrappers/gridnet.py b/python/griddly/util/rllib/wrappers/gridnet.py index 08d2a6554..a47b8ba53 100644 --- a/python/griddly/util/rllib/wrappers/gridnet.py +++ b/python/griddly/util/rllib/wrappers/gridnet.py @@ -24,11 +24,7 @@ def set_transform(self): self.num_action_logits = np.sum(cell_discrete_action_shape) - cell_multi_discretes = [] - for g in range(num_grid_locations): - cell_multi_discretes.extend(cell_discrete_action_shape) - - self.action_space = MultiDiscrete(cell_multi_discretes) + self.action_space = MultiDiscrete(cell_discrete_action_shape.repeat(self.width*self.height)) def step(self, action): # Un-grid the actions From 9e6e536745dfb53dd76b8851554a65d784e6689f Mon Sep 17 00:00:00 2001 From: Bam4d Date: Thu, 4 Mar 2021 17:16:41 +0000 Subject: [PATCH 24/34] cleaning up examples and doing some documentation --- python/examples/rllib/GriddlyRTS_test.yaml | 364 ------------------ python/examples/rllib/README.md | 50 ++- python/examples/rllib/rllib_RTS_UAS.py | 63 --- .../rllib/rllib_multiagent_taggers.py | 23 +- python/examples/rllib/rllib_single_agent.py | 23 +- python/examples/rllib/test_ma_tag.yaml | 178 --------- python/examples/rllib/test_ma_tag_coins.yaml | 219 ----------- python/examples/rllib/test_rts.yaml | 128 ------ python/griddly/GymWrapper.py | 2 + .../torch/conditional_masking_exploration.py | 77 ---- ...conditional_masking_gridnet_exploration.py | 97 ----- .../util/rllib/torch/impala/__init__.py | 0 .../torch/impala/im_vtrace_torch_policy.py | 129 ------- .../griddly/util/rllib/torch/impala/impala.py | 14 - .../util/rllib/torch/mixins/__init__.py | 0 .../mixins/invalid_action_policy_masking.py | 216 ----------- ...gridnet_masked_categorical_distribution.py | 225 ----------- python/griddly/util/rllib/wrappers/core.py | 8 +- python/griddly/util/rllib/wrappers/gridnet.py | 46 --- python/requirements.txt | 2 +- 20 files changed, 86 insertions(+), 1778 deletions(-) delete mode 100644 python/examples/rllib/GriddlyRTS_test.yaml delete mode 100644 python/examples/rllib/rllib_RTS_UAS.py delete mode 100644 python/examples/rllib/test_ma_tag.yaml delete mode 100644 python/examples/rllib/test_ma_tag_coins.yaml delete mode 100644 python/examples/rllib/test_rts.yaml delete mode 100644 python/griddly/util/rllib/torch/conditional_masking_exploration.py delete mode 100644 python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py delete mode 100644 python/griddly/util/rllib/torch/impala/__init__.py delete mode 100644 python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py delete mode 100644 python/griddly/util/rllib/torch/impala/impala.py delete mode 100644 python/griddly/util/rllib/torch/mixins/__init__.py delete mode 100644 python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py delete mode 100644 python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py delete mode 100644 python/griddly/util/rllib/wrappers/gridnet.py diff --git a/python/examples/rllib/GriddlyRTS_test.yaml b/python/examples/rllib/GriddlyRTS_test.yaml deleted file mode 100644 index 0c1f72e2c..000000000 --- a/python/examples/rllib/GriddlyRTS_test.yaml +++ /dev/null @@ -1,364 +0,0 @@ -Version: "0.1" -Environment: - Name: GriddlyRTS - Description: An RTS Game. There's aliens and stuff. - Observers: - Sprite2D: - TileSize: 16 - BackgroundTile: oryx/oryx_tiny_galaxy/tg_sliced/tg_world/tg_world_floor_panel_metal_a.png - Isometric: - TileSize: [ 32, 48 ] - BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png - IsoTileHeight: 16 - IsoTileDepth: 4 - Vector: - IncludePlayerId: true - IncludeVariables: true - Variables: - - Name: player_resources - InitialValue: 0 - PerPlayer: true - Player: - Count: 2 - Termination: - Lose: - - eq: [ base:count, 0 ] # If the player has no bases - Levels: - - | - H2 . - . H1 - - | - W W W W W W W W W W W W - W B2 . H2 . . . . . . . W - W . H2 . . . . . . . . W - W H2 . . . M M . . . . W - W . . . M M M M . . . W - W . . . . M M . . . H1 W - W . . . . . . . . H1 . W - W . . . . . . . H1 . B1 W - W W W W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - W . . B1 . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . H1 . . . . . . . . . . . . . . . . . . . . . . W - W . . . H1 . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . M M M M M . . . . . . . . . . . . . W - W . . . . . . . . . . M M M M M M . . . . . . . . . . . . W - W . . . . . . . . . M M . M M M M . . . . . . . . . . . . W - W . . . . . . . . . M . M M M . M . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . M M M M . . . . . . . . . . W - W . . . . . . . . . . . . . . M M M M M M . . . . . . . . W - W . . . . . . . . . . . . . M . . M . M M . . . . . . . . W - W . . . . . . . . . . . . . M M M M M M M . . . . . . . . W - W . . . . . . . . . . . . . . . M M M M . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . H2 . . . . . . W - W . . . . . . . . . . . . . . . . . . H2 . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . B2 . . W - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - W . . B1 . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . H1 . . . . . . . . . . . . . . . . . . . . . . W - W . . . H1 . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . M M M M M . . . . . . . . . . . . . W - W . . . . . . . . . . M M M M M M . . . . . . . . . . . . W - W . . . . . . . . . M M . M M M M . . . . . . . . . . . . W - W . . . . . . . . . M . M M M . M . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W - W W W W W W W W W . . . . . . . . . . . . . . W W W W W w W - W . . . . . . W W . . . w w w w w w . . . . . W W W W W w W - W . . . . . . . . . . . . . w w . . . . . . . W W W W W w W - W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W - W . . . . . . . . . . . . . . . . . . . . . . W W W W W w W - W . . . . . . . . . . . . . . M M M M . . . . W W W W W w W - W . . . . P1 w . . . . . . . . M M M M M M . W W W W W W W W - W . . . . P1 w . . . . . . . M . . M . M M . . . . . . . . W - W . . . . P2 w . . . . . . . M M M M M M M . . . . . . . . W - W . . . . P2 w . . . . . . . . . M M M M . . . . . . . . . W - W . . . . P1 w . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . H2 . . . . . . W - W . . . . . . . . . . . . . . . . . . H2 . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . B2 . . W - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - W . . . . M M M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . M M M M W - W . . . . . M M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . M M W - W . . . . . . M W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . H2 . . M W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . B2 H2 . M W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W W W w w W W W W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W W . . W W W W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W W W W W W W W W W W W W w w w w w w w w w w w w w W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W M . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W M M . . . . . . . . . . . . W . . . . . . . . . . . . M . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W M M M . . . . . . . . . . . W . . . . . . . . . . . M M M . . . . . . . . . . . W . . . . . . . . . . . . . . W - W W W W W W W W W W W . . . . W . . . . . . . . . . M M M M M . . . . . . . . . . W . . . . W W W W W W W W W W W - W . . . . . . . . . . . . . . W . . . . . . . . . . M M M M M . . . . . . . . . . W . . . . . . . . . . . M M M W - W . . . . . . . . . . . . . . W . . . . . . . . . . . M M M . . . . . . . . . . . W . . . . . . . . . . . . M M W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . M . . . . . . . . . . . . W . . . . . . . . . . . . . M W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W . . . . . . . . . . . . . . . . . . . . . . . . . W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . W w w w w w w w w w w w w w W W W W W W W W W W W W W . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W - W W W W . . W W W . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . W W W w w W W W W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W . . . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W M . H1 B1 . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W . . . . . . . W - W M . . H1 . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M . . . . . . W - W M M . . . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M M . . . . . W - W M M M M . . . W . . . . . . . . . . . . . . . . . . . w . . . . . . . . . . . . . . . . . . . W M M M . . . . W - W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W - -Actions: - # - Name: spawn_harvester - # InputMapping: - # Internal: true - # Behaviours: - # - Src: - # Object: base - # Commands: - # - spawn: harvester - # Dst: - # Object: _empty - # - # - # - Src: - # Object: base - # Dst: - # Object: [base, puncher, harvester, pusher, movable_wall] - # Commands: - # - exec: - # Action: spawn_harvester - # Delay: 1 - # Randomize: true - # - # # Harvester costs 5 resources to build - # - Name: build_harvester - # Behaviours: - # - Src: - # Preconditions: - # - gt: [player_resources, 5] - # Object: base - # Dst: - # Object: base - # Commands: - # - exec: - # Action: spawn_harvester - # Delay: 10 - # Randomize: true - - - - Name: gather - Behaviours: - - Src: - Object: harvester - Preconditions: - - lt: [ resources, 10 ] - Commands: - - incr: resources - - reward: 1 - Dst: - Object: minerals - Commands: - - decr: resources - - eq: - Arguments: [ resources, 0 ] - Commands: - - remove: true - - Src: - Object: harvester - Preconditions: - - gt: [ resources, 1 ] - - eq: [ src._playerId, dst._playerId ] - Commands: - - decr: resources - - reward: 1 - Dst: - Object: base - Commands: - - incr: player_resources - - - Name: move - Behaviours: - - Src: - Object: [ harvester, puncher, pusher, movable_wall ] - Commands: - - mov: _dest # mov will move the object, _dest is the destination location of the action - Dst: - Object: _empty - -# - Src: -# Object: pusher -# Commands: -# - mov: _dest # mov will move the object, _dest is the destination location of the action -# Dst: -# Object: [movable_wall, harvester, puncher] -# Commands: -# - cascade: _dest # reapply the same action to the dest location of the action - -# - Name: punch -# Behaviours: -# - Src: -# Object: puncher -# Commands: -# - reward: 1 -# Dst: -# Object: [puncher, harvester, pusher, base] -# Commands: -# - decr: health -# - eq: -# Arguments: [0, health] -# Commands: -# - remove: true - -Objects: - - Name: minerals - MapCharacter: M - Variables: - - Name: resources - InitialValue: 1 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_crystal_green.png - Block2D: - - Shape: triangle - Color: [ 0.0, 1.0, 0.0 ] - Scale: 1.0 - Isometric: - - Image: oryx/oryx_iso_dungeon/minerals-1.png - - - Name: harvester - MapCharacter: H - Variables: - - Name: resources - InitialValue: 0 - - Name: health - InitialValue: 10 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_jelly_d1.png - Block2D: - - Shape: square - Color: [ 0.6, 0.2, 0.2 ] - Scale: 0.5 - Isometric: - - Image: oryx/oryx_iso_dungeon/jelly-1.png - - - Name: pusher - MapCharacter: P - Variables: - - Name: health - InitialValue: 10 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_crawler_queen_d1.png - Block2D: - - Shape: square - Color: [ 0.2, 0.2, 0.6 ] - Scale: 1.0 - Isometric: - - Image: oryx/oryx_iso_dungeon/queen-1.png - - - Name: puncher - MapCharacter: p - Variables: - - Name: health - InitialValue: 5 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_beast_d1.png - Block2D: - - Color: [ 0.2, 0.6, 0.6 ] - Shape: square - Scale: 0.8 - Isometric: - - Image: oryx/oryx_iso_dungeon/beast-1.png - - - Name: fixed_wall - MapCharacter: W - Observers: - Sprite2D: - - TilingMode: WALL_2 # Will tile walls with two images - Image: - - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img33.png - - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img40.png - Block2D: - - Color: [ 0.5, 0.5, 0.5 ] - Shape: square - Isometric: - - Image: oryx/oryx_iso_dungeon/wall-grey-1.png - - - Name: movable_wall - MapCharacter: w - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img282.png - Block2D: - - Color: [ 0.8, 0.8, 0.8 ] - Shape: square - Isometric: - - Image: oryx/oryx_iso_dungeon/crate-1.png - - - Name: base - MapCharacter: B - Variables: - - Name: health - InitialValue: 10 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img324.png - Block2D: - - Color: [ 0.8, 0.8, 0.3 ] - Shape: triangle - Isometric: - - Image: oryx/oryx_iso_dungeon/base-1.png diff --git a/python/examples/rllib/README.md b/python/examples/rllib/README.md index bd8333906..204017139 100644 --- a/python/examples/rllib/README.md +++ b/python/examples/rllib/README.md @@ -1,5 +1,42 @@ -# EXERIMENTAL Griddly Rlib Examples +# Griddly Rlib Examples +Griddly provides a wrapper for all of its environments to be used with rllib! + +We include some examples here of how to use the Griddly `RLLibEnv` environment which wraps any Griddly environment. + +## RLLibEnv + +All Griddly environments can be used using the `RLLibEnv` custom RLLib environment. + +Firstly, the custom environment needs to be registered using: + +``` +register_env('your-env-name', RLlibEnv) +``` + +The environment can then be configured using `env_config` in RLLib's standard config method: + +``` +'env_config': { + + # Record a video of the environment at the following frequency of steps. + 'record_video_config': { + 'frequency': 100000 + }, + + # If all the levels in the environment are the same shape, this will set a random level every time the environment is reset + 'random_level_on_reset': True, + + # This is the yaml file of the GDY which defines the game + 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', + + # The observer type to use when recording videos + 'global_observer_type': gd.ObserverType.SPRITE_2D, + + # The maximum steps of the environment + 'max_steps': 1000 +} +``` ## Installation @@ -15,9 +52,18 @@ pip install -r requirements.txt For full documentation please visit: ### Single-Player + +The single player example can be modified to use any of the single player games, just change the yaml file and the +`RLLibEnv` will do the rest. Try it out by running: +`python rllib_single_agent.py` ### Multi-Agent +The multi-agent example plays the game of tag with multiple agents. + +`python rllib_multiagent_taggers.py` + +### RTS -### RTS \ No newline at end of file +Coming soon! \ No newline at end of file diff --git a/python/examples/rllib/rllib_RTS_UAS.py b/python/examples/rllib/rllib_RTS_UAS.py deleted file mode 100644 index 2ecd4e7f5..000000000 --- a/python/examples/rllib/rllib_RTS_UAS.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import sys - -import ray -from ray import tune -from ray.rllib.models import ModelCatalog -from ray.tune.registry import register_env - -from griddly import gd -from griddly.util.rllib.torch import GAPAgent -from griddly.util.rllib.torch.impala.impala import InvalidActionMaskingImpalaTrainer -from griddly.util.rllib.wrappers.core import RLlibEnv, RLlibMultiAgentWrapper - -if __name__ == '__main__': - sep = os.pathsep - os.environ['PYTHONPATH'] = sep.join(sys.path) - - ray.init(num_gpus=1, local_mode=True) - - env_name = 'ray-griddly-rts-env' - - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'GriddlyRTS_test.yaml') - - def _env_create(env_config): - env = RLlibEnv(env_config) - return RLlibMultiAgentWrapper(env, env_config) - - register_env(env_name, _env_create) - ModelCatalog.register_custom_model('GAP', GAPAgent) - - config = { - 'framework': 'torch', - 'num_workers': 1, - 'num_envs_per_worker': 1, - - # Must be set to false to use the InvalidActionMaskingPolicyMixin - "_use_trajectory_view_api": False, - 'model': { - 'custom_model': 'GAP', - 'custom_model_config': {} - }, - 'env': env_name, - 'env_config': { - # Tell the RLlib wrapper to use invalid action masking - 'invalid_action_masking': True, - - 'record_video_config': { - 'frequency': 100000 # number of rollouts - }, - - 'yaml_file': test_path, - 'global_observer_type': gd.ObserverType.ISOMETRIC, - 'level': 1, - 'max_steps': 1000, - }, - 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) - } - - stop = { - 'timesteps_total': 200000000, - } - - result = tune.run(InvalidActionMaskingImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index ea994bd23..acd8dec66 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -17,8 +17,6 @@ ray.init(num_gpus=1) - test_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_ma_tag_coins.yaml') - env_name = 'ray-ma-grouped-env' # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play @@ -30,13 +28,13 @@ def _create_env(env_config): ModelCatalog.register_custom_model('GAP', GAPAgent) + max_training_steps = 2000000 + config = { 'framework': 'torch', - 'num_workers': 6, + 'num_workers': 8, 'num_envs_per_worker': 1, - 'train_batch_size': 2048, - 'model': { 'custom_model': 'GAP', 'custom_model_config': {} @@ -48,19 +46,26 @@ def _create_env(env_config): 'player_done_variable': 'player_done', 'record_video_config': { - 'frequency': 10000 # number of rollouts + 'frequency': 20000 # number of rollouts }, - 'yaml_file': test_path, + 'yaml_file': 'Multi-Agent/robot_tag_4.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'level': 2, 'max_steps': 1000, }, - 'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) + 'entropy_coeff_schedule': [ + [0, 0.01], + [max_training_steps, 0.0] + ], + 'lr_schedule': [ + [0, 0.005], + [max_training_steps, 0.0] + ] } stop = { - 'timesteps_total': 2000000, + 'timesteps_total': max_training_steps, } result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 826a5c9c6..835f77cba 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -8,8 +8,8 @@ from ray.tune.registry import register_env from griddly import gd -from griddly.util.rllib import RLlibWrapper from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.wrappers.core import RLlibEnv if __name__ == '__main__': sep = os.pathsep @@ -19,9 +19,11 @@ env_name = "ray-griddly-env" - register_env(env_name, RLlibWrapper) + register_env(env_name, RLlibEnv) ModelCatalog.register_custom_model("GAP", GAPAgent) + max_training_steps = 100000000 + config = { 'framework': 'torch', 'num_workers': 8, @@ -33,22 +35,27 @@ }, 'env': env_name, 'env_config': { - # Uncomment this line to apply invalid action masking 'record_video_config': { - 'frequency': 10000 + 'frequency': 100000 }, + 'random_level_on_reset': True, 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, - 'level': 3, 'max_steps': 1000, }, - #'lr': tune.grid_search([0.0001, 0.0005, 0.001, 0.005]) + 'entropy_coeff_schedule': [ + [0, 0.01], + [max_training_steps, 0.0] + ], + 'lr_schedule': [ + [0, 0.0005], + [max_training_steps, 0.0] + ] } stop = { - # "training_iteration": 100, - "timesteps_total": 5000000, + "timesteps_total": max_training_steps, } result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/test_ma_tag.yaml b/python/examples/rllib/test_ma_tag.yaml deleted file mode 100644 index cf93e5132..000000000 --- a/python/examples/rllib/test_ma_tag.yaml +++ /dev/null @@ -1,178 +0,0 @@ -Version: "0.1" -Environment: - Name: Robot Battle - Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! - Observers: - Block2D: - TileSize: 24 - Sprite2D: - TileSize: 24 - BackgroundTile: oryx/oryx_fantasy/floor1-1.png - Vector: - IncludePlayerId: true - IncludeVariables: true - Variables: - - Name: player_done - InitialValue: 0 - PerPlayer: true - - Name: tagged_count - InitialValue: 0 - Player: - Count: 12 - Observer: - RotateWithAvatar: true - TrackAvatar: true - Height: 5 - Width: 5 - OffsetX: 0 - OffsetY: 0 - AvatarObject: tagger - Termination: - End: - - eq: [ tagged_count, 0 ] - - Levels: - - | - W W W W W W W W W - W . . f2 . f12 . . W - W . . . . . . . W - W f1 . f3 . f10 . f11 W - W . . . . . . . W - W . . . . . . . W - W f4 . f5 . f7 . f8 W - W . . . . . . . W - W . . f6 . f9 . . W - W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W - W . . . . . . . . . . . . . . . . . . . . W - W . . f2 . . . . . . . . . . . . . . f12 . . W - W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . W W W W W W . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . W W W W W W . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W - W . . f6 . . . . . . . . . . . . . . f9 . . W - W . . . . . . . . . . . . . . . . . . . . W - W W W W W W W W W W W W W W W W W W W W W W - - -Actions: - - # Taggers have a random chance of starting in a tagged state - - Name: initialize_is_tagged - InputMapping: - Internal: true - Inputs: - 1: - Description: Initialize Tagged - 2: - Description: Initialize Not Tagged - VectorToDest: [ -1, 0 ] - - Behaviours: - - Src: - Object: tagger - Preconditions: - - eq: [src._playerId, dst._playerId] - Commands: - - set_tile: 1 - - set: [ is_tagged, 1 ] - - incr: tagged_count - Dst: - Object: tagger - - - Name: tag - Behaviours: - - Src: - Object: tagger - Preconditions: - - eq: [ src.is_tagged, 1 ] - - eq: [ dst.is_tagged, 0 ] - Commands: - - reward: 2 - - set_tile: 0 - - set: [ is_tagged, 0 ] - Dst: - Object: tagger - Commands: - - set_tile: 1 - - set: [ is_tagged, 1 ] - - reward: -2 - - incr: times_tagged - - eq: - Arguments: [ times_tagged, 3 ] - Commands: - - set: [ player_done, 1 ] - - decr: tagged_count - - reward: -5 - - remove: true - - - Name: move - Behaviours: - - Src: - Object: tagger - Commands: - - mov: _dest # mov will move the object, _dest is the destination location of the action - Dst: - Object: _empty - -Objects: - - Name: tagger - MapCharacter: f - InitialActions: - - Action: initialize_is_tagged - Randomize: true - Variables: - - Name: is_tagged - InitialValue: 0 - - Name: times_tagged - InitialValue: 0 - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/avatars/robot1.png - - Image: oryx/oryx_fantasy/avatars/fireguy1.png - Block2D: - - Shape: triangle - Color: [ 0.2, 0.2, 0.9 ] - Scale: 0.5 - - Shape: triangle - Color: [ 0.9, 0.2, 0.2 ] - Scale: 1.0 - - - Name: fixed_wall - MapCharacter: W - Observers: - Sprite2D: - - TilingMode: WALL_16 - Image: - - oryx/oryx_fantasy/wall2-0.png - - oryx/oryx_fantasy/wall2-1.png - - oryx/oryx_fantasy/wall2-2.png - - oryx/oryx_fantasy/wall2-3.png - - oryx/oryx_fantasy/wall2-4.png - - oryx/oryx_fantasy/wall2-5.png - - oryx/oryx_fantasy/wall2-6.png - - oryx/oryx_fantasy/wall2-7.png - - oryx/oryx_fantasy/wall2-8.png - - oryx/oryx_fantasy/wall2-9.png - - oryx/oryx_fantasy/wall2-10.png - - oryx/oryx_fantasy/wall2-11.png - - oryx/oryx_fantasy/wall2-12.png - - oryx/oryx_fantasy/wall2-13.png - - oryx/oryx_fantasy/wall2-14.png - - oryx/oryx_fantasy/wall2-15.png - Block2D: - - Color: [ 0.5, 0.5, 0.5 ] - Shape: square diff --git a/python/examples/rllib/test_ma_tag_coins.yaml b/python/examples/rllib/test_ma_tag_coins.yaml deleted file mode 100644 index 3ef17e1ca..000000000 --- a/python/examples/rllib/test_ma_tag_coins.yaml +++ /dev/null @@ -1,219 +0,0 @@ -Version: "0.1" -Environment: - Name: Robot Battle - Description: 12 agents with their own egocentric viewpoint. Last agent standing wins!!! - Observers: - Block2D: - TileSize: 24 - Sprite2D: - TileSize: 24 - BackgroundTile: oryx/oryx_fantasy/floor1-1.png - Vector: - IncludePlayerId: true - IncludeVariables: true - Variables: - - Name: player_done - InitialValue: 0 - PerPlayer: true - - Name: tagged_count - InitialValue: 0 - Player: - Count: 12 - Observer: - RotateWithAvatar: true - TrackAvatar: true - Height: 5 - Width: 5 - OffsetX: 0 - OffsetY: 0 - AvatarObject: tagger - Termination: - End: - - eq: [ tagged_count, 0 ] - - Levels: - - | - W W W W W W W W W - W . . f2 . f12 . . W - W . . . . . . . W - W f1 . f3 . f10 . f11 W - W . . . . . . . W - W . . . . . . . W - W f4 . f5 . f7 . f8 W - W . . . . . . . W - W . . f6 . f9 . . W - W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W - W . . . . . . . . . . . . . . . . . . . . W - W . . f2 . . . . . . . . . . . . . . f12 . . W - W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . W W W W W W . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . W . . . . . . W . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . W W W W W W . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . . . . . . . . . . . . . . . . . . . . W - W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W - W . . f6 . . . . . . . . . . . . . . f9 . . W - W . . . . . . . . . . . . . . . . . . . . W - W W W W W W W W W W W W W W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W W W W W - W . . . . . . . . . . . . . . . . . . . . W - W . . f2 . . . . . . . . . . . . . . f12 . . W - W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . W W W W W W . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c W . . . . . . W c . . . c . W - W . c . . . c W . . . . . . W c . . . c . W - W . c . . . c W . . . . . . W c . . . c . W - W . c . . . c W . . . . . . W c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . W W W W W W . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . c . . . c . . . . . . . . c . . . c . W - W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W - W . . f6 . . . . . . . . . . . . . . f9 . . W - W . . . . . . . . . . . . . . . . . . . . W - W W W W W W W W W W W W W W W W W W W W W W - -Actions: - - # Taggers have a random chance of starting in a tagged state - - Name: initialize_is_tagged - InputMapping: - Internal: true - Inputs: - 1: - Description: Initialize Tagged - 2: - Description: Initialize Not Tagged - VectorToDest: [ -1, 0 ] - - Behaviours: - - Src: - Object: tagger - Preconditions: - - eq: [src._playerId, dst._playerId] - Commands: - - set_tile: 1 - - set: [ is_tagged, 1 ] - - incr: tagged_count - Dst: - Object: tagger - - - Name: tag - Behaviours: - - Src: - Object: tagger - Preconditions: - - eq: [ src.is_tagged, 1 ] - - eq: [ dst.is_tagged, 0 ] - Commands: - - reward: 2 - - set_tile: 0 - - set: [ is_tagged, 0 ] - Dst: - Object: tagger - Commands: - - set_tile: 1 - - set: [ is_tagged, 1 ] - - reward: -2 - - incr: times_tagged - - eq: - Arguments: [ times_tagged, 3 ] - Commands: - - set: [ player_done, 1 ] - - decr: tagged_count - - reward: -5 - - remove: true - - - Name: move - Behaviours: - - Src: - Object: tagger - Commands: - - mov: _dest # mov will move the object, _dest is the destination location of the action - Dst: - Object: _empty - - - Src: - Object: tagger - Commands: - - reward: 3 - Dst: - Object: coin - Commands: - - remove: true - -Objects: - - Name: coin - MapCharacter: c - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/jewel-3.png - Block2D: - - Shape: circle - Color: [0, 0.7, 0] - Scald: 1.0 - - - Name: tagger - MapCharacter: f - InitialActions: - - Action: initialize_is_tagged - Randomize: true - Variables: - - Name: is_tagged - InitialValue: 0 - - Name: times_tagged - InitialValue: 0 - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/avatars/robot1.png - - Image: oryx/oryx_fantasy/avatars/fireguy1.png - Block2D: - - Shape: triangle - Color: [ 0.2, 0.2, 0.9 ] - Scale: 0.5 - - Shape: triangle - Color: [ 0.9, 0.2, 0.2 ] - Scale: 1.0 - - - Name: fixed_wall - MapCharacter: W - Observers: - Sprite2D: - - TilingMode: WALL_16 - Image: - - oryx/oryx_fantasy/wall2-0.png - - oryx/oryx_fantasy/wall2-1.png - - oryx/oryx_fantasy/wall2-2.png - - oryx/oryx_fantasy/wall2-3.png - - oryx/oryx_fantasy/wall2-4.png - - oryx/oryx_fantasy/wall2-5.png - - oryx/oryx_fantasy/wall2-6.png - - oryx/oryx_fantasy/wall2-7.png - - oryx/oryx_fantasy/wall2-8.png - - oryx/oryx_fantasy/wall2-9.png - - oryx/oryx_fantasy/wall2-10.png - - oryx/oryx_fantasy/wall2-11.png - - oryx/oryx_fantasy/wall2-12.png - - oryx/oryx_fantasy/wall2-13.png - - oryx/oryx_fantasy/wall2-14.png - - oryx/oryx_fantasy/wall2-15.png - Block2D: - - Color: [ 0.5, 0.5, 0.5 ] - Shape: square \ No newline at end of file diff --git a/python/examples/rllib/test_rts.yaml b/python/examples/rllib/test_rts.yaml deleted file mode 100644 index fea084f0c..000000000 --- a/python/examples/rllib/test_rts.yaml +++ /dev/null @@ -1,128 +0,0 @@ -Version: "0.1" -Environment: - Name: TestRTS - Description: An RTS Game. There's aliens and stuff. - Observers: - Sprite2D: - TileSize: 32 - BackgroundTile: oryx/oryx_tiny_galaxy/tg_sliced/tg_world/tg_world_floor_panel_metal_a.png - Isometric: - TileSize: [32, 48] - BackgroundTile: oryx/oryx_iso_dungeon/floor-1.png - IsoTileHeight: 16 - IsoTileDepth: 4 - Vector: - IncludePlayerId: true - Variables: - - Name: player_resources - InitialValue: 0 - PerPlayer: true - Player: - Count: 2 - Termination: - Win: - - eq: [player_resources, 20] # First to 20 resources - - Levels: - - | - W W W W W W W W W W W W - W . . H2 . . . . . . . W - W . H2 . . . . . . . . W - W H2 . . . M M . . . . W - W . . . M M M M . . . W - W . . . . M M . . . H1 W - W . . . . . . . . H1 . W - W . . . . . . . H1 . . W - W W W W W W W W W W W W - -Actions: - - Name: gather - Behaviours: - - Src: - Object: harvester - Commands: - - incr: player_resources - - incr: resources - - reward: 1 - Dst: - Object: minerals - Commands: - - decr: resources - - eq: - Arguments: [resources, 0] - Commands: - - remove: true - - - # Steal resources from other players -# - Src: -# Preconditions: -# - neq: [ src._playerId, dst._playerId ] -# - gt: [dst.resources, 0] -# Object: harvester -# Commands: -# - incr: resources -# - reward: 1 -# Dst: -# Object: harvester -# Commands: -# - decr: resources -# - reward: -1 - - - - - Name: move - Behaviours: - - Src: - Object: harvester - Commands: - - mov: _dest # mov will move the object, _dest is the destination location of the action - Dst: - Object: _empty - -Objects: - - Name: minerals - MapCharacter: M - Variables: - - Name: resources - InitialValue: 5 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_items/tg_items_crystal_green.png - Block2D: - - Shape: triangle - Color: [0.0, 1.0, 0.0] - Scale: 1.0 - Isometric: - - Image: oryx/oryx_iso_dungeon/minerals-1.png - - - Name: harvester - MapCharacter: H - Variables: - - Name: resources - InitialValue: 0 - - Name: health - InitialValue: 10 - Observers: - Sprite2D: - - Image: oryx/oryx_tiny_galaxy/tg_sliced/tg_monsters/tg_monsters_jelly_d1.png - Block2D: - - Shape: square - Color: [0.6, 0.2, 0.2] - Scale: 0.5 - Isometric: - - Image: oryx/oryx_iso_dungeon/jelly-1.png - - - Name: fixed_wall - MapCharacter: W - Observers: - Sprite2D: - - TilingMode: WALL_2 # Will tile walls with two images - Image: - - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img33.png - - oryx/oryx_tiny_galaxy/tg_sliced/tg_world_fixed/img40.png - Block2D: - - Color: [0.5, 0.5, 0.5] - Shape: square - Isometric: - - Image: oryx/oryx_iso_dungeon/wall-grey-1.png diff --git a/python/griddly/GymWrapper.py b/python/griddly/GymWrapper.py index 3a64713a6..1879b82b2 100644 --- a/python/griddly/GymWrapper.py +++ b/python/griddly/GymWrapper.py @@ -46,6 +46,8 @@ def __init__(self, yaml_file=None, level=0, global_observer_type=gd.ObserverType self.gdy = gdy self.game = game + self.level_count = self.gdy.get_level_count() + self._players = [] self.player_count = self.gdy.get_player_count() diff --git a/python/griddly/util/rllib/torch/conditional_masking_exploration.py b/python/griddly/util/rllib/torch/conditional_masking_exploration.py deleted file mode 100644 index fa1f147b6..000000000 --- a/python/griddly/util/rllib/torch/conditional_masking_exploration.py +++ /dev/null @@ -1,77 +0,0 @@ -import numpy as np -import torch -from torch.distributions import Categorical - - -class TorchConditionalMaskingExploration(): - - def __init__(self, model, dist_class, dist_inputs, valid_action_trees): - self._valid_action_trees = valid_action_trees - self._dist_class = dist_class - - self._num_inputs = dist_inputs.shape[0] - self._action_space_shape = dist_class.keywords['input_lens'] - self._num_action_logits = np.sum(self._action_space_shape) - self._num_action_parts = len(self._action_space_shape) - - self._inputs_split = dist_inputs.split(tuple(self._action_space_shape), dim=1) - - def _mask_and_sample(self, options, logits): - - mask = torch.zeros([logits.shape[0]]) - mask[options] = 1 - - logits += torch.log(mask) - dist = Categorical(logits=logits) - sampled = dist.sample() - logp = dist.log_prob(sampled) - - return sampled, logits, logp, mask - - def get_actions_and_mask(self): - - actions = torch.zeros([self._num_inputs, self._num_action_parts]) - masked_logits = torch.zeros([self._num_inputs, self._num_action_logits]) - mask = torch.zeros([self._num_inputs, self._num_action_logits]) - logp_sums = torch.zeros([self._num_inputs]) - - for i in range(self._num_inputs): - if len(self._valid_action_trees) >= 1: - - subtree = self._valid_action_trees[i] - subtree_options = list(subtree.keys()) - - # In the case there are no available actions for the player - if len(subtree_options) == 0: - subtree = {0: {0: {0: [0]}}} - subtree_options = [0] - - logp_parts = torch.zeros([self._num_action_parts]) - mask_offset = 0 - for a in range(self._num_action_parts): - dist_part = self._inputs_split[a] - sampled, masked_part_logits, logp, mask_part = self._mask_and_sample(subtree_options, dist_part[i]) - - # Set the action and the mask for each part of the action - actions[i, a] = sampled - masked_logits[i, mask_offset:mask_offset + self._action_space_shape[a]] = masked_part_logits - mask[i, mask_offset:mask_offset + self._action_space_shape[a]] = mask_part - - logp_parts[a] = logp - - if mask_part.sum() == 0: - raise RuntimeError('mask calculated incorrectly') - - mask_offset += self._action_space_shape[a] - - if isinstance(subtree, dict): - subtree = subtree[int(sampled)] - if isinstance(subtree, dict): - subtree_options = list(subtree.keys()) - else: - # Leaf nodes with action_id list - subtree_options = subtree - - logp_sums[i] = torch.sum(logp_parts) - - return actions, masked_logits, logp_sums, mask diff --git a/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py b/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py deleted file mode 100644 index 83d83846e..000000000 --- a/python/griddly/util/rllib/torch/conditional_masking_gridnet_exploration.py +++ /dev/null @@ -1,97 +0,0 @@ -import torch -from torch.distributions import Categorical - - -class TorchConditionalMaskingGridnetExploration(): - - def __init__(self, model, dist_class, dist_inputs, valid_action_trees): - self._valid_action_trees = valid_action_trees - self._dist_class = dist_class - - self._num_inputs = dist_inputs.shape[0] - - self._width = model.width - self._height = model.height - self._grid_action_shape = model.grid_action_shape - self._grid_action_parts = len(self._grid_action_shape) - self._grid_channels = model.grid_channels - - self._dist_inputs_reshaped = dist_inputs.reshape(-1, self._grid_channels, self._width, self._height) - - def _mask_and_sample(self, options, logits): - - mask = torch.zeros([logits.shape[0]]) - mask[options] = 1 - - logits += torch.log(mask) - dist = Categorical(logits=logits) - sampled = dist.sample() - logp = dist.log_prob(sampled) - - return sampled, logits, logp, mask - - def get_actions_and_mask(self): - - actions = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) - masked_logits = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) - mask = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) - logp_sums = torch.zeros([self._num_inputs]) - - # Initialize the masks to NOP - mask[:, 0, :, :] = 1 - for action_logit_size in self._grid_action_shape[:-1]: - mask[:, action_logit_size, :, :] = 0 - - for i in range(self._num_inputs): - if len(self._valid_action_trees) >= 1: - - x_tree = self._valid_action_trees[i] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - - # only bother with calculating actions for things that are possible in the grid - - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - dist_input = self._dist_inputs_reshaped[i, :, x, y] - dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) - - logp_parts = torch.zeros([self._grid_action_parts]) - mask_offset = 0 - for a in range(self._grid_action_parts): - dist_part = dist_input_split[a] - - sampled, masked_part_logits, logp, mask_part = self._mask_and_sample( - subtree_options, - dist_part - ) - - # Set the action and the mask for each part of the action - logit_end = mask_offset + self._grid_action_shape[a] - actions[i, a] = sampled - masked_logits[i, mask_offset:logit_end, x, y] = masked_part_logits - mask[i, mask_offset:logit_end, x, y] = mask_part - - logp_parts[a] = logp - - if mask_part.sum() == 0: - raise RuntimeError('mask calculated incorrectly') - - mask_offset += self._grid_action_shape[a] - - if isinstance(subtree, dict): - subtree = subtree[int(sampled)] - if isinstance(subtree, dict): - subtree_options = list(subtree.keys()) - else: - # Leaf nodes with action_id list - subtree_options = subtree - - logp_sums[i] += torch.sum(logp_parts) - - return actions.flatten(1), masked_logits.flatten(1), logp_sums, mask.flatten(1) diff --git a/python/griddly/util/rllib/torch/impala/__init__.py b/python/griddly/util/rllib/torch/impala/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py b/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py deleted file mode 100644 index 253ade08f..000000000 --- a/python/griddly/util/rllib/torch/impala/im_vtrace_torch_policy.py +++ /dev/null @@ -1,129 +0,0 @@ -import functools -import logging - -import gym -import numpy as np -import ray -from ray.rllib.agents.impala.vtrace_torch_policy import VTraceTorchPolicy, make_time_major, VTraceLoss -from ray.rllib.models.torch.torch_action_dist import TorchCategorical -from ray.rllib.policy.sample_batch import SampleBatch -from ray.rllib.policy.torch_policy import LearningRateSchedule, \ - EntropyCoeffSchedule -from ray.rllib.utils.framework import try_import_torch -from ray.rllib.utils.torch_ops import sequence_mask - -from griddly.util.rllib.torch.mixins.invalid_action_policy_masking import InvalidActionMaskingPolicyMixin -from griddly.util.rllib.torch.torch_gridnet_masked_categorical_distribution import GridnetMaskedCategoricalDistribution - -torch, nn = try_import_torch() - -logger = logging.getLogger(__name__) - -def build_invalid_masking_vtrace_loss(policy, model, dist_class, train_batch): - model_out, _ = model.from_batch(train_batch) - - if isinstance(policy.action_space, gym.spaces.Discrete): - is_multidiscrete = False - output_hidden_shape = [policy.action_space.n] - elif isinstance(policy.action_space, gym.spaces.MultiDiscrete): - is_multidiscrete = True - output_hidden_shape = policy.action_space.nvec.astype(np.int32) - else: - is_multidiscrete = False - output_hidden_shape = 1 - - def _make_time_major(*args, **kw): - return make_time_major(policy, train_batch.get("seq_lens"), *args, - **kw) - - actions = train_batch[SampleBatch.ACTIONS] - dones = train_batch[SampleBatch.DONES] - rewards = train_batch[SampleBatch.REWARDS] - behaviour_action_logp = train_batch[SampleBatch.ACTION_LOGP] - behaviour_logits = train_batch[SampleBatch.ACTION_DIST_INPUTS] - - valid_action_mask = train_batch['valid_action_mask'] - - if 'seq_lens' in train_batch: - max_seq_len = policy.config['rollout_fragment_length'] - mask_orig = sequence_mask(train_batch["seq_lens"], max_seq_len) - mask = torch.reshape(mask_orig, [-1]) - else: - mask = torch.ones_like(rewards) - - valid_action_mask[torch.where(mask == False)] = 1 - - model_out += torch.log(valid_action_mask) - action_dist = dist_class(model_out, model) - - if isinstance(output_hidden_shape, (list, tuple, np.ndarray)): - unpacked_behaviour_logits = torch.split( - behaviour_logits, list(output_hidden_shape), dim=1) - unpacked_outputs = torch.split( - model_out, list(output_hidden_shape), dim=1) - else: - unpacked_behaviour_logits = torch.chunk( - behaviour_logits, output_hidden_shape, dim=1) - unpacked_outputs = torch.chunk(model_out, output_hidden_shape, dim=1) - values = model.value_function() - - # Prepare actions for loss. - loss_actions = actions if is_multidiscrete else torch.unsqueeze( - actions, dim=1) - - # Inputs are reshaped from [B * T] => [T - 1, B] for V-trace calc. - policy.loss = VTraceLoss( - actions=_make_time_major(loss_actions, drop_last=True), - actions_logp=_make_time_major( - action_dist.logp(actions), drop_last=True), - actions_entropy=_make_time_major( - action_dist.entropy(), drop_last=True), - dones=_make_time_major(dones, drop_last=True), - behaviour_action_logp=_make_time_major( - behaviour_action_logp, drop_last=True), - behaviour_logits=_make_time_major( - unpacked_behaviour_logits, drop_last=True), - target_logits=_make_time_major(unpacked_outputs, drop_last=True), - discount=policy.config["gamma"], - rewards=_make_time_major(rewards, drop_last=True), - values=_make_time_major(values, drop_last=True), - bootstrap_value=_make_time_major(values)[-1], - dist_class=TorchCategorical if is_multidiscrete else dist_class, - model=model, - valid_mask=_make_time_major(mask, drop_last=True), - config=policy.config, - vf_loss_coeff=policy.config["vf_loss_coeff"], - entropy_coeff=policy.entropy_coeff, - clip_rho_threshold=policy.config["vtrace_clip_rho_threshold"], - clip_pg_rho_threshold=policy.config["vtrace_clip_pg_rho_threshold"]) - - return policy.loss.total_loss - - -def setup_mixins(policy, obs_space, action_space, config): - InvalidActionMaskingPolicyMixin.__init__(policy) - EntropyCoeffSchedule.__init__(policy, config["entropy_coeff"], - config["entropy_coeff_schedule"]) - LearningRateSchedule.__init__(policy, config["lr"], config["lr_schedule"]) - - -def postprocess_episode(policy, sample_batch, other_agent_batches, episode): - if 'valid_action_trees' not in sample_batch: - sample_batch['valid_action_trees'] = np.array([{} for _ in range(sample_batch['action_dist_inputs'].shape[0])]) - - if 'valid_action_mask' not in sample_batch: - sample_batch['valid_action_mask'] = np.ones_like(sample_batch['action_dist_inputs']) - else: - if sample_batch['valid_action_mask'].sum() == 0: - raise RuntimeError('empty action mask') - return sample_batch - - -InvalidMaskingVTraceTorchPolicy = VTraceTorchPolicy.with_updates( - name="InvalidMaskingVTraceTorchPolicy", - loss_fn=build_invalid_masking_vtrace_loss, - get_default_config=lambda: ray.rllib.agents.impala.impala.DEFAULT_CONFIG, - before_init=setup_mixins, - postprocess_fn=postprocess_episode, - mixins=[LearningRateSchedule, EntropyCoeffSchedule, InvalidActionMaskingPolicyMixin] -) diff --git a/python/griddly/util/rllib/torch/impala/impala.py b/python/griddly/util/rllib/torch/impala/impala.py deleted file mode 100644 index a315f8a60..000000000 --- a/python/griddly/util/rllib/torch/impala/impala.py +++ /dev/null @@ -1,14 +0,0 @@ -from ray.rllib.agents.impala import ImpalaTrainer - -from griddly.util.rllib.torch.impala.im_vtrace_torch_policy import InvalidMaskingVTraceTorchPolicy - - -def get_policy_class(config): - if config['framework'] == 'torch': - return InvalidMaskingVTraceTorchPolicy - else: - raise NotImplementedError('Tensorflow not supported') - - -InvalidActionMaskingImpalaTrainer = ImpalaTrainer.with_updates(default_policy=InvalidMaskingVTraceTorchPolicy, - get_policy_class=get_policy_class) \ No newline at end of file diff --git a/python/griddly/util/rllib/torch/mixins/__init__.py b/python/griddly/util/rllib/torch/mixins/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py b/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py deleted file mode 100644 index fef11b206..000000000 --- a/python/griddly/util/rllib/torch/mixins/invalid_action_policy_masking.py +++ /dev/null @@ -1,216 +0,0 @@ -import functools -from typing import Callable, Dict, List, Optional, Tuple, Type, Union - -import tree -from ray.rllib import SampleBatch, Policy -from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper -from ray.rllib.utils import override -from ray.rllib.utils.torch_ops import convert_to_torch_tensor, convert_to_non_torch_type -from ray.rllib.utils.typing import TensorType - -import numpy as np -import torch - -from griddly.util.rllib.torch.conditional_masking_exploration import TorchConditionalMaskingExploration -from griddly.util.rllib.torch.torch_gridnet_masked_categorical_distribution import GridnetMaskedCategoricalDistribution - - -class InvalidActionMaskingPolicyMixin: - """ - The info_batch contains the valid action trees. compute_actions is the only part of rllib that can has access to the - info_batch, therefore we have to override it to explore/exploit valid actions. - """ - - @override(Policy) - def compute_actions_from_input_dict( - self, - input_dict: Dict[str, TensorType], - explore: bool = None, - timestep: Optional[int] = None, - episodes: Optional[List["MultiAgentEpisode"]] = None, - **kwargs) -> \ - Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: - - with torch.no_grad(): - # Pass lazy (torch) tensor dict to Model as `input_dict`. - input_dict = self._lazy_tensor_dict(input_dict) - # Pack internal state inputs into (separate) list. - state_batches = [ - input_dict[k] for k in input_dict.keys() if "state_in" in k[:8] - ] - # Calculate RNN sequence lengths. - seq_lens = np.array([1] * len(input_dict["obs"])) \ - if state_batches else None - - # Call the exploration before_compute_actions hook. - self.exploration.before_compute_actions( - explore=explore, timestep=timestep) - - dist_inputs, state_out = self.model(input_dict, state_batches, - seq_lens) - # Extract the tree from the info batch - valid_action_trees = [] - for info in input_dict[SampleBatch.INFOS]: - if isinstance(info, torch.Tensor): - valid_action_trees.append({0: {0: {0: [0]}}}) - elif 'valid_action_trees' in info: - valid_action_trees.append(info['valid_action_trees']) - - if hasattr(self.model, 'grid_channels'): - - self.dist_class = functools.partial(GridnetMaskedCategoricalDistribution, - valid_action_trees=valid_action_trees) - action_dist = self.dist_class(dist_inputs, self.model) - - # Get the exploration action from the forward results. - actions, logp = \ - self.exploration.get_exploration_action( - action_distribution=action_dist, - timestep=timestep, - explore=explore) - #if - masked_logits = action_dist.sampled_masked_logits() - mask = action_dist.sampled_action_masks() - - # exploration = TorchConditionalMaskingGridnetExploration( - # self.model, - # self.dist_class, - # dist_inputs, - # valid_action_trees, - # ) - else: - exploration = TorchConditionalMaskingExploration( - self.model, - self.dist_class, - dist_inputs, - valid_action_trees, - ) - - actions, masked_logits, logp, mask = exploration.get_actions_and_mask() - - input_dict[SampleBatch.ACTIONS] = actions - - extra_fetches = { - 'valid_action_mask': mask, - 'valid_action_trees': np.array(valid_action_trees), - } - - # Action-dist inputs. - if dist_inputs is not None: - extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = masked_logits - - # Action-logp and action-prob. - if logp is not None: - extra_fetches[SampleBatch.ACTION_PROB] = \ - torch.exp(logp.float()) - extra_fetches[SampleBatch.ACTION_LOGP] = logp - - # Update our global timestep by the batch size. - self.global_timestep += len(input_dict[SampleBatch.CUR_OBS]) - - return convert_to_non_torch_type((actions, state_out, extra_fetches)) - - @override(Policy) - def compute_actions( - self, - obs_batch: Union[List[TensorType], TensorType], - state_batches: Optional[List[TensorType]] = None, - prev_action_batch: Union[List[TensorType], TensorType] = None, - prev_reward_batch: Union[List[TensorType], TensorType] = None, - info_batch: Optional[Dict[str, list]] = None, - episodes: Optional[List["MultiAgentEpisode"]] = None, - explore: Optional[bool] = None, - timestep: Optional[int] = None, - **kwargs) -> \ - Tuple[TensorType, List[TensorType], Dict[str, TensorType]]: - - if not self.config['env_config'].get('invalid_action_masking', False): - raise RuntimeError('invalid_action_masking must be set to True in env_config to use this mixin') - - explore = explore if explore is not None else self.config["explore"] - timestep = timestep if timestep is not None else self.global_timestep - - with torch.no_grad(): - seq_lens = torch.ones(len(obs_batch), dtype=torch.int32) - input_dict = self._lazy_tensor_dict({ - SampleBatch.CUR_OBS: np.asarray(obs_batch), - "is_training": False, - }) - if prev_action_batch is not None: - input_dict[SampleBatch.PREV_ACTIONS] = \ - np.asarray(prev_action_batch) - if prev_reward_batch is not None: - input_dict[SampleBatch.PREV_REWARDS] = \ - np.asarray(prev_reward_batch) - state_batches = [ - convert_to_torch_tensor(s, self.device) - for s in (state_batches or []) - ] - - # Call the exploration before_compute_actions hook. - self.exploration.before_compute_actions( - explore=explore, timestep=timestep) - - dist_inputs, state_out = self.model(input_dict, state_batches, - seq_lens) - # Extract the tree from the info batch - valid_action_trees = [] - for info in info_batch: - if 'valid_action_trees' in info: - valid_action_trees.append(info['valid_action_trees']) - else: - valid_action_trees.append({0: {0: {0: [0]}}}) - - if hasattr(self.model, 'grid_channels'): - - self.dist_class = functools.partial(GridnetMaskedCategoricalDistribution, valid_action_trees=valid_action_trees) - action_dist = self.dist_class(dist_inputs, self.model) - - # Get the exploration action from the forward results. - actions, logp = \ - self.exploration.get_exploration_action( - action_distribution=action_dist, - timestep=timestep, - explore=explore) - - if explore: - dist_inputs = action_dist.sampled_masked_logits() - mask = action_dist.sampled_action_masks() - - # exploration = TorchConditionalMaskingGridnetExploration( - # self.model, - # self.dist_class, - # dist_inputs, - # valid_action_trees, - # ) - else: - exploration = TorchConditionalMaskingExploration( - self.model, - self.dist_class, - dist_inputs, - valid_action_trees, - ) - - actions, masked_logits, logp, mask = exploration.get_actions_and_mask() - - input_dict[SampleBatch.ACTIONS] = actions - - extra_fetches = { - 'valid_action_mask': mask, - 'valid_action_trees': np.array(valid_action_trees), - } - - # Action-dist inputs. - if dist_inputs is not None: - extra_fetches[SampleBatch.ACTION_DIST_INPUTS] = dist_inputs - - # Action-logp and action-prob. - if logp is not None: - extra_fetches[SampleBatch.ACTION_PROB] = \ - torch.exp(logp.float()) - extra_fetches[SampleBatch.ACTION_LOGP] = logp - - # Update our global timestep by the batch size. - self.global_timestep += len(input_dict[SampleBatch.CUR_OBS]) - - return convert_to_non_torch_type((actions, state_out, extra_fetches)) diff --git a/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py b/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py deleted file mode 100644 index 307cd4c0c..000000000 --- a/python/griddly/util/rllib/torch/torch_gridnet_masked_categorical_distribution.py +++ /dev/null @@ -1,225 +0,0 @@ -from ray.rllib.models import ActionDistribution -from ray.rllib.models.torch.torch_action_dist import TorchDistributionWrapper -import numpy as np -import torch -from ray.rllib.utils.typing import TensorType -from torch.distributions import Categorical - - -class GridnetMaskedCategoricalDistribution(TorchDistributionWrapper): - - def __init__(self, dist_inputs, model, valid_action_trees): - self._valid_action_trees = valid_action_trees - - self._num_inputs = dist_inputs.shape[0] - - self._width = model.width - self._height = model.height - self._grid_action_shape = model.grid_action_shape - self._grid_action_parts = len(self._grid_action_shape) - self._grid_channels = model.grid_channels - self._dist_inputs_reshaped = dist_inputs.reshape(-1, self._grid_channels, self._width, self._height) - - def _is_dummy(self): - # Hack for annoying rllib optimization where it tries to work out waht values you are using in your algorithm - return not isinstance(self._valid_action_trees[0], dict) - - def _reset(self): - self._last_sample = torch.zeros([self._num_inputs, self._grid_action_parts, self._width, self._height]) - self._last_sample_masks = torch.zeros([self._num_inputs, self._grid_channels, self._width, self._height]) - - self._last_sample_logp = torch.zeros([self._num_inputs]) - - # Initialize the masks to NOP - self._last_sample_masks[:, 0, :, :] = 1 - for action_logit_size in self._grid_action_shape[:-1]: - self._last_sample_masks[:, action_logit_size, :, :] = 1 - - self._last_sample_masked_logits = self._dist_inputs_reshaped + torch.log(self._last_sample_masks) - - def deterministic_sample(self): - - self._reset() - - for i in range(self._num_inputs): - x_tree = self._valid_action_trees[i] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - dist_input = self._dist_inputs_reshaped[i, :, x, y] - dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) - - entropy_parts = torch.zeros([self._grid_action_parts]) - for a in range(self._grid_action_parts): - dist_part = dist_input_split[a] - mask = torch.zeros([dist_part.shape[0]]) - mask[subtree_options] = 1 - - logits = dist_part + torch.log(mask) - dist = Categorical(logits=logits) - sampled = torch.argmax(dist.probs) - - self._last_sample[i, a, x, y] = sampled - - return self._last_sample - - def logp(self, actions): - - logp_sums = torch.zeros([self._num_inputs]) - - if self._is_dummy(): - return logp_sums - - grid_actions = actions.reshape(-1, self._grid_action_parts, self._width, self._height) - - # run through the trees and only calculate where valid actions exist - for i in range(self._num_inputs): - x_tree = self._valid_action_trees[i] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - dist_input = self._dist_inputs_reshaped[i, :, x, y] - dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) - - logp_parts = torch.zeros([self._grid_action_parts]) - for a in range(self._grid_action_parts): - dist_part = dist_input_split[a] - mask = torch.zeros([dist_part.shape[0]]) - mask[subtree_options] = 1 - - logits = dist_part + torch.log(mask) - dist = Categorical(logits=logits) - logp_parts[a] = dist.log_prob(grid_actions[i, a, x, y]) - - logp_sums[i] += torch.sum(logp_parts) - - return logp_sums - - def entropy(self): - - entropy_sums = torch.zeros([self._num_inputs]) - - if self._is_dummy(): - return entropy_sums - - # Entropy for everything by the valid action locations will be 0 - # as the masks only allow selection of a single action - for i in range(self._num_inputs): - x_tree = self._valid_action_trees[i] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - dist_input = self._dist_inputs_reshaped[i, :, x, y] - dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) - - entropy_parts = torch.zeros([self._grid_action_parts]) - for a in range(self._grid_action_parts): - dist_part = dist_input_split[a] - mask = torch.zeros([dist_part.shape[0]]) - mask[subtree_options] = 1 - - logits = dist_part + torch.log(mask) - dist = Categorical(logits=logits) - entropy_parts[a] = dist.entropy() - - entropy_sums[i] += torch.sum(entropy_parts) - - return entropy_sums - - def kl(self, other: ActionDistribution): - pass - - def _mask_and_sample(self, options, logits): - - mask = torch.zeros([logits.shape[0]]) - mask[options] = 1 - - logits += torch.log(mask) - dist = Categorical(logits=logits) - sampled = dist.sample() - logp = dist.log_prob(sampled) - - return sampled, logp, logits, mask - - def sample(self): - - self._reset() - - for i in range(self._num_inputs): - if len(self._valid_action_trees) >= 1: - - x_tree = self._valid_action_trees[i] - - # In the case there are no available actions for the player - if len(x_tree) == 0: - continue - - # Only bother with calculating actions for things that are possible in the grid - for x, y_tree in x_tree.items(): - for y, subtree in y_tree.items(): - - subtree_options = list(subtree.keys()) - - dist_input = self._dist_inputs_reshaped[i, :, x, y] - dist_input_split = torch.split(dist_input, tuple(self._grid_action_shape), dim=0) - - logp_parts = torch.zeros([self._grid_action_parts]) - mask_offset = 0 - for a in range(self._grid_action_parts): - dist_part = dist_input_split[a] - - sampled, logp, masked_part_logits, mask_part = self._mask_and_sample( - subtree_options, - dist_part - ) - - # Set the action and the mask for each part of the action - logit_end = mask_offset + self._grid_action_shape[a] - self._last_sample[i, a, x, y] = sampled - self._last_sample_masked_logits[i, mask_offset:logit_end, x, y] = masked_part_logits - self._last_sample_masks[i, mask_offset:logit_end, x, y] = mask_part - - logp_parts[a] = logp - - if mask_part.sum() == 0: - raise RuntimeError('mask calculated incorrectly') - - mask_offset += self._grid_action_shape[a] - - if isinstance(subtree, dict): - subtree = subtree[int(sampled)] - if isinstance(subtree, dict): - subtree_options = list(subtree.keys()) - else: - # Leaf nodes with action_id list - subtree_options = subtree - self._last_sample_logp[i] += torch.sum(logp_parts) - - return self._last_sample.flatten(1) - - def sampled_action_masks(self): - return self._last_sample_masks.flatten(1) - - def sampled_masked_logits(self): - return self._last_sample_masked_logits.flatten(1) - - def sampled_action_logp(self): - return self._last_sample_logp diff --git a/python/griddly/util/rllib/wrappers/core.py b/python/griddly/util/rllib/wrappers/core.py index 55fc918ca..3c96ee659 100644 --- a/python/griddly/util/rllib/wrappers/core.py +++ b/python/griddly/util/rllib/wrappers/core.py @@ -59,6 +59,7 @@ def __init__(self, env_config): self.invalid_action_masking = env_config.get('invalid_action_masking', False) self._record_video_config = env_config.get('record_video_config', None) + self._random_level_on_reset = env_config.get('random_level_on_reset', False) super().reset() @@ -133,8 +134,8 @@ def set_transform(self): self.action_space = self.action_space[0] self.observation_space = gym.spaces.Box( - self.observation_space.low.transpose((1, 2, 0)), - self.observation_space.high.transpose((1, 2, 0)), + self.observation_space.low.transpose((1, 2, 0)).astype(np.float), + self.observation_space.high.transpose((1, 2, 0)).astype(np.float), dtype=np.float, ) @@ -142,6 +143,9 @@ def set_transform(self): self.width = self.observation_space.shape[1] def reset(self, **kwargs): + + if self._random_level_on_reset: + kwargs['level_id'] = np.random.choice(self.level_count) observation = super().reset(**kwargs) self.set_transform() diff --git a/python/griddly/util/rllib/wrappers/gridnet.py b/python/griddly/util/rllib/wrappers/gridnet.py deleted file mode 100644 index a47b8ba53..000000000 --- a/python/griddly/util/rllib/wrappers/gridnet.py +++ /dev/null @@ -1,46 +0,0 @@ -from gym.spaces import MultiDiscrete -import numpy as np -from griddly.util.rllib.wrappers.core import RLlibEnv - - -class RLlibGridnetEnv(RLlibEnv): - - def __init__(self, env_config): - super().__init__(env_config) - - def set_transform(self): - """ - :return: - """ - - super().set_transform() - - num_grid_locations = self.width * self.height - cell_discrete_action_shape = self.action_space.nvec[2:] - - self.num_action_parts = 1 - if self.action_count > 1: - self.num_action_parts += 1 - - self.num_action_logits = np.sum(cell_discrete_action_shape) - - self.action_space = MultiDiscrete(cell_discrete_action_shape.repeat(self.width*self.height)) - - def step(self, action): - # Un-grid the actions - - grid_actions = action.reshape(-1, self.num_action_parts, self.width, self.height) - - # We have a HxW grid of actions, but we only need the actions for the valid locations in the grid, - # so we pull them out - multi_actions = [] - for p in range(self.player_count): - unit_actions = [] - x_tree = self.last_valid_action_trees[p] - for x, y_tree in x_tree.items(): - for y, _ in y_tree.items(): - unit_action = grid_actions[p, :, x, y] - unit_actions.append([x, y, *unit_action]) - multi_actions.append(unit_actions) - - return super().step(multi_actions) diff --git a/python/requirements.txt b/python/requirements.txt index 29745992b..bcc842968 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,5 +1,5 @@ numpy>=1.19.1 -gym==0.17.2 +gym==0.17.3 pyyaml>=5.3.1 imageio>=2.9.0 pygame>=2.0.0 From d014918411a910026d729ed9f33792ed49d23b50 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Fri, 5 Mar 2021 16:19:48 +0000 Subject: [PATCH 25/34] working on rllib docs --- docs/conf.py | 1 + docs/games/Bait/img/Bait-level-Vector-0.png | Bin 179 -> 173 bytes docs/games/Bait/img/Bait-level-Vector-1.png | Bin 310 -> 315 bytes docs/games/Bait/img/Bait-level-Vector-2.png | Bin 383 -> 378 bytes docs/games/Bait/img/Bait-level-Vector-3.png | Bin 372 -> 378 bytes docs/games/Bait/img/Bait-level-Vector-4.png | Bin 264 -> 264 bytes .../Bait/img/Bait-tile-avatar-Vector.png | Bin 77 -> 78 bytes docs/games/Bait/img/Bait-tile-box-Vector.png | Bin 78 -> 78 bytes docs/games/Bait/img/Bait-tile-goal-Vector.png | Bin 78 -> 78 bytes docs/games/Bait/img/Bait-tile-hole-Vector.png | Bin 77 -> 78 bytes docs/games/Bait/img/Bait-tile-key-Vector.png | Bin 78 -> 78 bytes .../Bait/img/Bait-tile-mushroom-Vector.png | Bin 78 -> 77 bytes docs/games/Bait/img/Bait-tile-wall-Vector.png | Bin 78 -> 77 bytes docs/games/Bait/index.rst | 9 + .../img/Bait_With_Keys-level-Vector-0.png | Bin 179 -> 173 bytes .../img/Bait_With_Keys-level-Vector-1.png | Bin 310 -> 315 bytes .../img/Bait_With_Keys-level-Vector-2.png | Bin 383 -> 378 bytes .../img/Bait_With_Keys-level-Vector-3.png | Bin 372 -> 378 bytes .../img/Bait_With_Keys-level-Vector-4.png | Bin 264 -> 264 bytes .../img/Bait_With_Keys-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../img/Bait_With_Keys-tile-box-Vector.png | Bin 78 -> 78 bytes .../img/Bait_With_Keys-tile-goal-Vector.png | Bin 78 -> 78 bytes .../img/Bait_With_Keys-tile-hole-Vector.png | Bin 77 -> 78 bytes .../img/Bait_With_Keys-tile-key-Vector.png | Bin 78 -> 78 bytes .../Bait_With_Keys-tile-mushroom-Vector.png | Bin 78 -> 77 bytes .../img/Bait_With_Keys-tile-wall-Vector.png | Bin 78 -> 77 bytes docs/games/Bait_With_Keys/index.rst | 9 + ...Butterflies_and_Spiders-level-Vector-0.png | Bin 477 -> 477 bytes ...Butterflies_and_Spiders-level-Vector-1.png | Bin 491 -> 516 bytes ...Butterflies_and_Spiders-level-Vector-2.png | Bin 500 -> 504 bytes ...Butterflies_and_Spiders-level-Vector-3.png | Bin 444 -> 455 bytes ...Butterflies_and_Spiders-level-Vector-4.png | Bin 447 -> 453 bytes ...Butterflies_and_Spiders-level-Vector-5.png | Bin 515 -> 516 bytes ...Butterflies_and_Spiders-level-Vector-6.png | Bin 528 -> 554 bytes ...Butterflies_and_Spiders-level-Vector-7.png | Bin 500 -> 504 bytes ...Butterflies_and_Spiders-level-Vector-8.png | Bin 462 -> 460 bytes ...Butterflies_and_Spiders-level-Vector-9.png | Bin 490 -> 505 bytes ...lies_and_Spiders-tile-butterfly-Vector.png | Bin 77 -> 78 bytes ...rflies_and_Spiders-tile-catcher-Vector.png | Bin 78 -> 77 bytes ...erflies_and_Spiders-tile-cocoon-Vector.png | Bin 78 -> 77 bytes ...tterflies_and_Spiders-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Butterflies_and_Spiders/index.rst | 9 + .../Clusters/img/Clusters-level-Vector-0.png | Bin 346 -> 365 bytes .../Clusters/img/Clusters-level-Vector-1.png | Bin 383 -> 381 bytes .../Clusters/img/Clusters-level-Vector-2.png | Bin 373 -> 383 bytes .../Clusters/img/Clusters-level-Vector-3.png | Bin 376 -> 391 bytes .../Clusters/img/Clusters-level-Vector-4.png | Bin 372 -> 379 bytes .../img/Clusters-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../img/Clusters-tile-blue_box-Vector.png | Bin 78 -> 77 bytes .../img/Clusters-tile-green_block-Vector.png | Bin 78 -> 77 bytes .../img/Clusters-tile-green_box-Vector.png | Bin 78 -> 77 bytes .../img/Clusters-tile-red_block-Vector.png | Bin 78 -> 78 bytes .../img/Clusters-tile-red_box-Vector.png | Bin 78 -> 78 bytes .../img/Clusters-tile-spike-Vector.png | Bin 77 -> 78 bytes .../img/Clusters-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Clusters/index.rst | 9 + .../img/Cook_Me_Pasta-level-Vector-0.png | Bin 342 -> 350 bytes .../img/Cook_Me_Pasta-level-Vector-1.png | Bin 342 -> 350 bytes .../img/Cook_Me_Pasta-level-Vector-2.png | Bin 383 -> 391 bytes .../img/Cook_Me_Pasta-level-Vector-3.png | Bin 398 -> 407 bytes .../img/Cook_Me_Pasta-level-Vector-4.png | Bin 393 -> 411 bytes .../img/Cook_Me_Pasta-level-Vector-5.png | Bin 400 -> 422 bytes .../img/Cook_Me_Pasta-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...ook_Me_Pasta-tile-boiling_water-Vector.png | Bin 78 -> 77 bytes .../img/Cook_Me_Pasta-tile-lock-Vector.png | Bin 77 -> 77 bytes .../Cook_Me_Pasta-tile-raw_pasta-Vector.png | Bin 78 -> 78 bytes .../img/Cook_Me_Pasta-tile-tomato-Vector.png | Bin 78 -> 78 bytes .../img/Cook_Me_Pasta-tile-tuna-Vector.png | Bin 78 -> 77 bytes .../img/Cook_Me_Pasta-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Cook_Me_Pasta/index.rst | 9 + docs/games/Doggo/img/Doggo-level-Vector-0.png | Bin 164 -> 170 bytes docs/games/Doggo/img/Doggo-level-Vector-1.png | Bin 180 -> 187 bytes docs/games/Doggo/img/Doggo-level-Vector-2.png | Bin 195 -> 202 bytes docs/games/Doggo/img/Doggo-level-Vector-3.png | Bin 240 -> 249 bytes docs/games/Doggo/img/Doggo-level-Vector-4.png | Bin 422 -> 428 bytes .../Doggo/img/Doggo-tile-doggo-Vector.png | Bin 78 -> 77 bytes .../Doggo/img/Doggo-tile-stick-Vector.png | Bin 78 -> 78 bytes .../Doggo/img/Doggo-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Doggo/index.rst | 9 + .../img/Drunk_Dwarf-level-Vector-0.png | Bin 200 -> 201 bytes .../img/Drunk_Dwarf-level-Vector-1.png | Bin 216 -> 224 bytes .../img/Drunk_Dwarf-level-Vector-2.png | Bin 263 -> 262 bytes .../img/Drunk_Dwarf-level-Vector-3.png | Bin 334 -> 336 bytes .../img/Drunk_Dwarf-level-Vector-4.png | Bin 551 -> 555 bytes .../img/Drunk_Dwarf-tile-bookshelf-Vector.png | Bin 78 -> 78 bytes .../img/Drunk_Dwarf-tile-chair-Vector.png | Bin 78 -> 77 bytes .../Drunk_Dwarf-tile-coffin_bed-Vector.png | Bin 77 -> 77 bytes .../img/Drunk_Dwarf-tile-doggo-Vector.png | Bin 78 -> 78 bytes .../img/Drunk_Dwarf-tile-door-Vector.png | Bin 78 -> 78 bytes .../Drunk_Dwarf-tile-drunk_dwarf-Vector.png | Bin 77 -> 78 bytes .../img/Drunk_Dwarf-tile-key-Vector.png | Bin 78 -> 77 bytes .../img/Drunk_Dwarf-tile-table-Vector.png | Bin 78 -> 78 bytes .../img/Drunk_Dwarf-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Drunk_Dwarf/index.rst | 9 + .../Eyeball/img/Eyeball-level-Vector-0.png | Bin 520 -> 517 bytes .../img/Eyeball-tile-eye_drops-Vector.png | Bin 78 -> 78 bytes .../img/Eyeball-tile-eyeball-Vector.png | Bin 78 -> 77 bytes .../Eyeball/img/Eyeball-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Eyeball/index.rst | 9 + .../img/GriddlyRTS-level-Vector-0.png | Bin 895 -> 891 bytes .../img/GriddlyRTS-level-Vector-1.png | Bin 999 -> 994 bytes .../img/GriddlyRTS-level-Vector-2.png | Bin 2211 -> 2212 bytes .../img/GriddlyRTS-tile-base-Vector.png | Bin 78 -> 78 bytes .../img/GriddlyRTS-tile-fixed_wall-Vector.png | Bin 78 -> 78 bytes .../img/GriddlyRTS-tile-harvester-Vector.png | Bin 77 -> 78 bytes .../img/GriddlyRTS-tile-minerals-Vector.png | Bin 77 -> 78 bytes .../GriddlyRTS-tile-movable_wall-Vector.png | Bin 78 -> 77 bytes .../img/GriddlyRTS-tile-puncher-Vector.png | Bin 78 -> 78 bytes .../img/GriddlyRTS-tile-pusher-Vector.png | Bin 78 -> 78 bytes docs/games/GriddlyRTS/index.rst | 12 + .../img/Heal_Or_Die-level-Vector-0.png | Bin 459 -> 438 bytes .../img/Heal_Or_Die-level-Vector-1.png | Bin 746 -> 715 bytes .../img/Heal_Or_Die-tile-healer-Vector.png | Bin 78 -> 78 bytes .../img/Heal_Or_Die-tile-hole-Vector.png | Bin 78 -> 78 bytes .../img/Heal_Or_Die-tile-mountain-Vector.png | Bin 77 -> 77 bytes .../img/Heal_Or_Die-tile-warrior-Vector.png | Bin 78 -> 78 bytes docs/games/Heal_Or_Die/index.rst | 12 + .../img/Kill_The_King-level-Vector-0.png | Bin 718 -> 726 bytes .../img/Kill_The_King-tile-archer-Vector.png | Bin 78 -> 78 bytes .../img/Kill_The_King-tile-forest-Vector.png | Bin 77 -> 78 bytes .../img/Kill_The_King-tile-healer-Vector.png | Bin 78 -> 78 bytes .../img/Kill_The_King-tile-hole-Vector.png | Bin 78 -> 78 bytes .../img/Kill_The_King-tile-king-Vector.png | Bin 78 -> 78 bytes .../Kill_The_King-tile-mountain-Vector.png | Bin 77 -> 77 bytes .../img/Kill_The_King-tile-warrior-Vector.png | Bin 78 -> 78 bytes .../img/Kill_The_King-tile-water-Vector.png | Bin 77 -> 78 bytes docs/games/Kill_The_King/index.rst | 12 + .../img/Labyrinth-level-Vector-0.png | Bin 491 -> 496 bytes .../img/Labyrinth-level-Vector-1.png | Bin 480 -> 484 bytes .../img/Labyrinth-level-Vector-2.png | Bin 473 -> 484 bytes .../img/Labyrinth-level-Vector-3.png | Bin 457 -> 463 bytes .../img/Labyrinth-level-Vector-4.png | Bin 469 -> 469 bytes .../img/Labyrinth-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../img/Labyrinth-tile-exit-Vector.png | Bin 78 -> 78 bytes .../img/Labyrinth-tile-trap-Vector.png | Bin 78 -> 78 bytes .../img/Labyrinth-tile-wall-Vector.png | Bin 78 -> 77 bytes docs/games/Labyrinth/index.rst | 9 + ...rtially_Observable_Bait-level-Vector-0.png | Bin 179 -> 173 bytes ...rtially_Observable_Bait-level-Vector-1.png | Bin 310 -> 315 bytes ...rtially_Observable_Bait-level-Vector-2.png | Bin 383 -> 378 bytes ...rtially_Observable_Bait-level-Vector-3.png | Bin 372 -> 378 bytes ...rtially_Observable_Bait-level-Vector-4.png | Bin 264 -> 264 bytes ...lly_Observable_Bait-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...tially_Observable_Bait-tile-box-Vector.png | Bin 78 -> 78 bytes ...ially_Observable_Bait-tile-goal-Vector.png | Bin 78 -> 78 bytes ...ially_Observable_Bait-tile-hole-Vector.png | Bin 77 -> 78 bytes ...tially_Observable_Bait-tile-key-Vector.png | Bin 78 -> 78 bytes ...y_Observable_Bait-tile-mushroom-Vector.png | Bin 78 -> 77 bytes ...ially_Observable_Bait-tile-wall-Vector.png | Bin 78 -> 77 bytes .../games/Partially_Observable_Bait/index.rst | 9 + ...lly_Observable_Clusters-level-Vector-0.png | Bin 346 -> 365 bytes ...lly_Observable_Clusters-level-Vector-1.png | Bin 383 -> 381 bytes ...lly_Observable_Clusters-level-Vector-2.png | Bin 373 -> 383 bytes ...lly_Observable_Clusters-level-Vector-3.png | Bin 376 -> 391 bytes ...lly_Observable_Clusters-level-Vector-4.png | Bin 372 -> 379 bytes ...Observable_Clusters-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...servable_Clusters-tile-blue_box-Vector.png | Bin 78 -> 77 bytes ...vable_Clusters-tile-green_block-Vector.png | Bin 78 -> 77 bytes ...ervable_Clusters-tile-green_box-Vector.png | Bin 78 -> 77 bytes ...ervable_Clusters-tile-red_block-Vector.png | Bin 78 -> 78 bytes ...bservable_Clusters-tile-red_box-Vector.png | Bin 78 -> 78 bytes ..._Observable_Clusters-tile-spike-Vector.png | Bin 77 -> 78 bytes ...y_Observable_Clusters-tile-wall-Vector.png | Bin 77 -> 78 bytes .../Partially_Observable_Clusters/index.rst | 9 + ...bservable_Cook_Me_Pasta-level-Vector-0.png | Bin 342 -> 350 bytes ...bservable_Cook_Me_Pasta-level-Vector-1.png | Bin 342 -> 350 bytes ...bservable_Cook_Me_Pasta-level-Vector-2.png | Bin 383 -> 391 bytes ...bservable_Cook_Me_Pasta-level-Vector-3.png | Bin 398 -> 407 bytes ...bservable_Cook_Me_Pasta-level-Vector-4.png | Bin 393 -> 411 bytes ...bservable_Cook_Me_Pasta-level-Vector-5.png | Bin 400 -> 422 bytes ...vable_Cook_Me_Pasta-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...ook_Me_Pasta-tile-boiling_water-Vector.png | Bin 78 -> 77 bytes ...ervable_Cook_Me_Pasta-tile-lock-Vector.png | Bin 77 -> 77 bytes ...le_Cook_Me_Pasta-tile-raw_pasta-Vector.png | Bin 78 -> 78 bytes ...vable_Cook_Me_Pasta-tile-tomato-Vector.png | Bin 78 -> 78 bytes ...ervable_Cook_Me_Pasta-tile-tuna-Vector.png | Bin 78 -> 77 bytes ...ervable_Cook_Me_Pasta-tile-wall-Vector.png | Bin 77 -> 78 bytes .../index.rst | 9 + ...ly_Observable_Labyrinth-level-Vector-0.png | Bin 491 -> 496 bytes ...ly_Observable_Labyrinth-level-Vector-1.png | Bin 480 -> 484 bytes ...ly_Observable_Labyrinth-level-Vector-2.png | Bin 473 -> 484 bytes ...ly_Observable_Labyrinth-level-Vector-3.png | Bin 457 -> 463 bytes ...ly_Observable_Labyrinth-level-Vector-4.png | Bin 469 -> 469 bytes ...bservable_Labyrinth-tile-avatar-Vector.png | Bin 77 -> 78 bytes ..._Observable_Labyrinth-tile-exit-Vector.png | Bin 78 -> 78 bytes ..._Observable_Labyrinth-tile-trap-Vector.png | Bin 78 -> 78 bytes ..._Observable_Labyrinth-tile-wall-Vector.png | Bin 78 -> 77 bytes .../Partially_Observable_Labyrinth/index.rst | 9 + ..._Observable_Sokoban_-_2-level-Vector-0.png | Bin 213 -> 215 bytes ..._Observable_Sokoban_-_2-level-Vector-1.png | Bin 236 -> 234 bytes ..._Observable_Sokoban_-_2-level-Vector-2.png | Bin 218 -> 215 bytes ..._Observable_Sokoban_-_2-level-Vector-3.png | Bin 196 -> 197 bytes ..._Observable_Sokoban_-_2-level-Vector-4.png | Bin 285 -> 281 bytes ...ervable_Sokoban_-_2-tile-avatar-Vector.png | Bin 78 -> 78 bytes ...Observable_Sokoban_-_2-tile-box-Vector.png | Bin 77 -> 77 bytes ...e_Sokoban_-_2-tile-box_in_place-Vector.png | Bin 77 -> 78 bytes ...bservable_Sokoban_-_2-tile-hole-Vector.png | Bin 78 -> 77 bytes ...bservable_Sokoban_-_2-tile-wall-Vector.png | Bin 78 -> 78 bytes .../index.rst | 9 + ...tially_Observable_Zelda-level-Vector-0.png | Bin 285 -> 285 bytes ...tially_Observable_Zelda-level-Vector-1.png | Bin 428 -> 427 bytes ...tially_Observable_Zelda-level-Vector-2.png | Bin 371 -> 364 bytes ...ly_Observable_Zelda-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...ally_Observable_Zelda-tile-goal-Vector.png | Bin 78 -> 78 bytes ...ly_Observable_Zelda-tile-spider-Vector.png | Bin 78 -> 78 bytes ...ally_Observable_Zelda-tile-wall-Vector.png | Bin 78 -> 77 bytes .../Partially_Observable_Zelda/index.rst | 9 + ...y_Observable_Zen_Puzzle-level-Vector-0.png | Bin 292 -> 293 bytes ...y_Observable_Zen_Puzzle-level-Vector-1.png | Bin 302 -> 301 bytes ...y_Observable_Zen_Puzzle-level-Vector-2.png | Bin 315 -> 314 bytes ...y_Observable_Zen_Puzzle-level-Vector-3.png | Bin 305 -> 322 bytes ...y_Observable_Zen_Puzzle-level-Vector-4.png | Bin 328 -> 323 bytes ...servable_Zen_Puzzle-tile-avatar-Vector.png | Bin 77 -> 78 bytes ...servable_Zen_Puzzle-tile-ground-Vector.png | Bin 78 -> 78 bytes ...Observable_Zen_Puzzle-tile-rock-Vector.png | Bin 78 -> 77 bytes .../Partially_Observable_Zen_Puzzle/index.rst | 9 + .../img/Push_Mania-level-Vector-0.png | Bin 286 -> 289 bytes .../img/Push_Mania-level-Vector-1.png | Bin 447 -> 446 bytes .../img/Push_Mania-tile-hole-Vector.png | Bin 77 -> 77 bytes .../img/Push_Mania-tile-pusher-Vector.png | Bin 78 -> 78 bytes docs/games/Push_Mania/index.rst | 12 + .../img/Random_butterflies-level-Vector-0.png | Bin 477 -> 477 bytes .../img/Random_butterflies-level-Vector-1.png | Bin 491 -> 516 bytes .../img/Random_butterflies-level-Vector-2.png | Bin 500 -> 504 bytes .../img/Random_butterflies-level-Vector-3.png | Bin 444 -> 455 bytes .../img/Random_butterflies-level-Vector-4.png | Bin 447 -> 453 bytes .../img/Random_butterflies-level-Vector-5.png | Bin 515 -> 516 bytes .../img/Random_butterflies-level-Vector-6.png | Bin 528 -> 554 bytes .../img/Random_butterflies-level-Vector-7.png | Bin 500 -> 504 bytes .../img/Random_butterflies-level-Vector-8.png | Bin 462 -> 460 bytes .../img/Random_butterflies-level-Vector-9.png | Bin 490 -> 505 bytes ...ndom_butterflies-tile-butterfly-Vector.png | Bin 77 -> 78 bytes ...Random_butterflies-tile-catcher-Vector.png | Bin 78 -> 77 bytes .../Random_butterflies-tile-cocoon-Vector.png | Bin 78 -> 77 bytes .../Random_butterflies-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Random_butterflies/index.rst | 11 +- .../img/Robot_Tag_12v12-level-Block2D-0.png | Bin 0 -> 1256 bytes .../img/Robot_Tag_12v12-level-Block2D-1.png | Bin 0 -> 2945 bytes .../img/Robot_Tag_12v12-level-Block2D-2.png | Bin 0 -> 2660 bytes .../img/Robot_Tag_12v12-level-Block2D-3.png | Bin 0 -> 6113 bytes .../img/Robot_Tag_12v12-level-Sprite2D-0.png | Bin 0 -> 5380 bytes .../img/Robot_Tag_12v12-level-Sprite2D-1.png | Bin 0 -> 12604 bytes .../img/Robot_Tag_12v12-level-Sprite2D-2.png | Bin 0 -> 15770 bytes .../img/Robot_Tag_12v12-level-Sprite2D-3.png | Bin 0 -> 38142 bytes .../img/Robot_Tag_12v12-level-Vector-0.png | Bin 0 -> 266 bytes .../img/Robot_Tag_12v12-level-Vector-1.png | Bin 0 -> 620 bytes .../img/Robot_Tag_12v12-level-Vector-2.png | Bin 0 -> 664 bytes .../img/Robot_Tag_12v12-level-Vector-3.png | Bin 0 -> 1410 bytes ...obot_Tag_12v12-tile-fixed_wall-Block2D.png | Bin 0 -> 91 bytes ...bot_Tag_12v12-tile-fixed_wall-Sprite2D.png | Bin 0 -> 244 bytes ...Robot_Tag_12v12-tile-fixed_wall-Vector.png | Bin 0 -> 77 bytes ...t_Tag_12v12-tile-moveable_wall-Block2D.png | Bin 0 -> 91 bytes ..._Tag_12v12-tile-moveable_wall-Sprite2D.png | Bin 0 -> 293 bytes ...ot_Tag_12v12-tile-moveable_wall-Vector.png | Bin 0 -> 77 bytes .../Robot_Tag_12v12-tile-tagger-Block2D.png | Bin 0 -> 144 bytes .../Robot_Tag_12v12-tile-tagger-Sprite2D.png | Bin 0 -> 453 bytes .../Robot_Tag_12v12-tile-tagger-Vector.png | Bin 0 -> 77 bytes docs/games/Robot_Tag_12v12/index.rst | 445 ++++++++++++++++++ .../img/Robot_Tag_4v4-level-Block2D-0.png | Bin 0 -> 993 bytes .../img/Robot_Tag_4v4-level-Block2D-1.png | Bin 0 -> 2120 bytes .../img/Robot_Tag_4v4-level-Block2D-2.png | Bin 0 -> 2201 bytes .../img/Robot_Tag_4v4-level-Block2D-3.png | Bin 0 -> 5301 bytes .../img/Robot_Tag_4v4-level-Sprite2D-0.png | Bin 0 -> 4058 bytes .../img/Robot_Tag_4v4-level-Sprite2D-1.png | Bin 0 -> 10499 bytes .../img/Robot_Tag_4v4-level-Sprite2D-2.png | Bin 0 -> 13013 bytes .../img/Robot_Tag_4v4-level-Sprite2D-3.png | Bin 0 -> 36166 bytes .../img/Robot_Tag_4v4-level-Vector-0.png | Bin 0 -> 252 bytes .../img/Robot_Tag_4v4-level-Vector-1.png | Bin 0 -> 610 bytes .../img/Robot_Tag_4v4-level-Vector-2.png | Bin 0 -> 660 bytes .../img/Robot_Tag_4v4-level-Vector-3.png | Bin 0 -> 1386 bytes .../Robot_Tag_4v4-tile-fixed_wall-Block2D.png | Bin 0 -> 91 bytes ...Robot_Tag_4v4-tile-fixed_wall-Sprite2D.png | Bin 0 -> 244 bytes .../Robot_Tag_4v4-tile-fixed_wall-Vector.png | Bin 0 -> 77 bytes ...bot_Tag_4v4-tile-moveable_wall-Block2D.png | Bin 0 -> 91 bytes ...ot_Tag_4v4-tile-moveable_wall-Sprite2D.png | Bin 0 -> 293 bytes ...obot_Tag_4v4-tile-moveable_wall-Vector.png | Bin 0 -> 77 bytes .../img/Robot_Tag_4v4-tile-tagger-Block2D.png | Bin 0 -> 144 bytes .../Robot_Tag_4v4-tile-tagger-Sprite2D.png | Bin 0 -> 603 bytes .../img/Robot_Tag_4v4-tile-tagger-Vector.png | Bin 0 -> 77 bytes docs/games/Robot_Tag_4v4/index.rst | 445 ++++++++++++++++++ .../img/Robot_Tag_8v8-level-Block2D-0.png | Bin 0 -> 1895 bytes .../img/Robot_Tag_8v8-level-Block2D-1.png | Bin 0 -> 2708 bytes .../img/Robot_Tag_8v8-level-Block2D-2.png | Bin 0 -> 3129 bytes .../img/Robot_Tag_8v8-level-Block2D-3.png | Bin 0 -> 6641 bytes .../img/Robot_Tag_8v8-level-Sprite2D-0.png | Bin 0 -> 6411 bytes .../img/Robot_Tag_8v8-level-Sprite2D-1.png | Bin 0 -> 13629 bytes .../img/Robot_Tag_8v8-level-Sprite2D-2.png | Bin 0 -> 17277 bytes .../img/Robot_Tag_8v8-level-Sprite2D-3.png | Bin 0 -> 40453 bytes .../img/Robot_Tag_8v8-level-Vector-0.png | Bin 0 -> 273 bytes .../img/Robot_Tag_8v8-level-Vector-1.png | Bin 0 -> 616 bytes .../img/Robot_Tag_8v8-level-Vector-2.png | Bin 0 -> 665 bytes .../img/Robot_Tag_8v8-level-Vector-3.png | Bin 0 -> 1431 bytes .../Robot_Tag_8v8-tile-fixed_wall-Block2D.png | Bin 0 -> 91 bytes ...Robot_Tag_8v8-tile-fixed_wall-Sprite2D.png | Bin 0 -> 244 bytes .../Robot_Tag_8v8-tile-fixed_wall-Vector.png | Bin 0 -> 77 bytes ...bot_Tag_8v8-tile-moveable_wall-Block2D.png | Bin 0 -> 91 bytes ...ot_Tag_8v8-tile-moveable_wall-Sprite2D.png | Bin 0 -> 293 bytes ...obot_Tag_8v8-tile-moveable_wall-Vector.png | Bin 0 -> 77 bytes .../img/Robot_Tag_8v8-tile-tagger-Block2D.png | Bin 0 -> 203 bytes .../Robot_Tag_8v8-tile-tagger-Sprite2D.png | Bin 0 -> 453 bytes .../img/Robot_Tag_8v8-tile-tagger-Vector.png | Bin 0 -> 77 bytes docs/games/Robot_Tag_8v8/index.rst | 445 ++++++++++++++++++ .../Sokoban/img/Sokoban-level-Vector-0.png | Bin 290 -> 299 bytes .../Sokoban/img/Sokoban-level-Vector-1.png | Bin 338 -> 343 bytes .../Sokoban/img/Sokoban-level-Vector-2.png | Bin 340 -> 323 bytes .../Sokoban/img/Sokoban-level-Vector-3.png | Bin 317 -> 322 bytes .../Sokoban/img/Sokoban-level-Vector-4.png | Bin 237 -> 240 bytes .../Sokoban/img/Sokoban-level-Vector-5.png | Bin 286 -> 279 bytes .../img/Sokoban-tile-avatar-Vector.png | Bin 78 -> 78 bytes .../Sokoban/img/Sokoban-tile-box-Vector.png | Bin 77 -> 78 bytes .../Sokoban/img/Sokoban-tile-hole-Vector.png | Bin 78 -> 77 bytes .../Sokoban/img/Sokoban-tile-wall-Vector.png | Bin 78 -> 78 bytes docs/games/Sokoban/index.rst | 9 + .../img/Sokoban_-_2-level-Vector-0.png | Bin 213 -> 215 bytes .../img/Sokoban_-_2-level-Vector-1.png | Bin 236 -> 234 bytes .../img/Sokoban_-_2-level-Vector-2.png | Bin 218 -> 215 bytes .../img/Sokoban_-_2-level-Vector-3.png | Bin 196 -> 197 bytes .../img/Sokoban_-_2-level-Vector-4.png | Bin 285 -> 281 bytes .../img/Sokoban_-_2-tile-avatar-Vector.png | Bin 78 -> 78 bytes .../img/Sokoban_-_2-tile-box-Vector.png | Bin 77 -> 77 bytes .../Sokoban_-_2-tile-box_in_place-Vector.png | Bin 77 -> 78 bytes .../img/Sokoban_-_2-tile-hole-Vector.png | Bin 78 -> 77 bytes .../img/Sokoban_-_2-tile-wall-Vector.png | Bin 78 -> 78 bytes docs/games/Sokoban_-_2/index.rst | 9 + .../img/Spider_Nest-level-Vector-0.png | Bin 1268 -> 1295 bytes .../img/Spider_Nest-tile-gem-Vector.png | Bin 78 -> 78 bytes .../img/Spider_Nest-tile-lava-Vector.png | Bin 78 -> 77 bytes .../img/Spider_Nest-tile-nest-Vector.png | Bin 78 -> 78 bytes .../img/Spider_Nest-tile-spider-Vector.png | Bin 77 -> 78 bytes .../img/Spider_Nest-tile-wall-Vector.png | Bin 77 -> 77 bytes docs/games/Spider_Nest/index.rst | 9 + .../Spiders/img/Spiders-level-Vector-0.png | Bin 187 -> 186 bytes .../Spiders/img/Spiders-level-Vector-1.png | Bin 207 -> 207 bytes .../Spiders/img/Spiders-level-Vector-2.png | Bin 240 -> 237 bytes .../Spiders/img/Spiders-level-Vector-3.png | Bin 301 -> 301 bytes .../Spiders/img/Spiders-level-Vector-4.png | Bin 513 -> 508 bytes .../Spiders/img/Spiders-tile-gem-Vector.png | Bin 78 -> 77 bytes .../Spiders/img/Spiders-tile-gnome-Vector.png | Bin 78 -> 78 bytes .../img/Spiders-tile-spider-Vector.png | Bin 78 -> 78 bytes .../Spiders/img/Spiders-tile-wall-Vector.png | Bin 77 -> 78 bytes docs/games/Spiders/index.rst | 9 + docs/games/Zelda/img/Zelda-level-Vector-0.png | Bin 285 -> 285 bytes docs/games/Zelda/img/Zelda-level-Vector-1.png | Bin 342 -> 343 bytes docs/games/Zelda/img/Zelda-level-Vector-2.png | Bin 371 -> 364 bytes .../Zelda/img/Zelda-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../Zelda/img/Zelda-tile-goal-Vector.png | Bin 78 -> 78 bytes .../Zelda/img/Zelda-tile-spider-Vector.png | Bin 78 -> 78 bytes .../Zelda/img/Zelda-tile-wall-Vector.png | Bin 78 -> 77 bytes docs/games/Zelda/index.rst | 9 + .../img/Zelda_Sequential-level-Vector-0.png | Bin 285 -> 285 bytes .../img/Zelda_Sequential-level-Vector-1.png | Bin 342 -> 343 bytes .../img/Zelda_Sequential-level-Vector-2.png | Bin 386 -> 381 bytes .../Zelda_Sequential-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../img/Zelda_Sequential-tile-goal-Vector.png | Bin 78 -> 78 bytes .../Zelda_Sequential-tile-spider-Vector.png | Bin 78 -> 78 bytes .../img/Zelda_Sequential-tile-wall-Vector.png | Bin 78 -> 77 bytes docs/games/Zelda_Sequential/index.rst | 9 + .../img/Zen_Puzzle-level-Vector-0.png | Bin 292 -> 293 bytes .../img/Zen_Puzzle-level-Vector-1.png | Bin 302 -> 301 bytes .../img/Zen_Puzzle-level-Vector-2.png | Bin 315 -> 314 bytes .../img/Zen_Puzzle-level-Vector-3.png | Bin 305 -> 322 bytes .../img/Zen_Puzzle-level-Vector-4.png | Bin 328 -> 323 bytes .../img/Zen_Puzzle-tile-avatar-Vector.png | Bin 77 -> 78 bytes .../img/Zen_Puzzle-tile-ground-Vector.png | Bin 78 -> 78 bytes .../img/Zen_Puzzle-tile-rock-Vector.png | Bin 78 -> 77 bytes docs/games/Zen_Puzzle/index.rst | 9 + docs/games/img/Robot_Tag_12v12-taster.png | Bin 0 -> 38142 bytes docs/games/img/Robot_Tag_4v4-taster.png | Bin 0 -> 36166 bytes docs/games/img/Robot_Tag_8v8-taster.png | Bin 0 -> 40453 bytes docs/games/index.rst | 37 ++ docs/index.rst | 12 + .../Observers/Block2D/TileSize/index.rst | 2 +- .../Environment/Observers/Block2D/index.rst | 2 +- .../Observers/Sprite2D/TileSize/index.rst | 2 +- .../Environment/Observers/Sprite2D/index.rst | 2 +- .../Vector/IncludePlayerId/index.rst | 17 + .../Vector/IncludeRotation/index.rst | 17 + .../Vector/IncludeVariables/index.rst | 17 + .../Environment/Observers/Vector/index.rst | 37 ++ .../GDY/Environment/Observers/index.rst | 3 + .../GDY/Environment/Termination/End/index.rst | 32 ++ .../GDY/Environment/Termination/index.rst | 3 + docs/requirements.txt | 3 +- docs/rllib/intro/index.rst | 153 ++++++ docs/rllib/multi-agent/index.rst | 18 + docs/rllib/rts/index.rst | 7 + docs/rllib/single-agent/index.rst | 81 ++++ 387 files changed, 2041 insertions(+), 6 deletions(-) create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-0.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-1.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-2.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-3.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-0.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-1.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-2.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-3.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-0.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-1.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-2.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-3.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-fixed_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-fixed_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-fixed_wall-Vector.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-moveable_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-moveable_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-moveable_wall-Vector.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-tagger-Block2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-tagger-Sprite2D.png create mode 100644 docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-tagger-Vector.png create mode 100644 docs/games/Robot_Tag_12v12/index.rst create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-0.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-1.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-2.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-3.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-0.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-1.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-2.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-3.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-0.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-1.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-2.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-3.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-fixed_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-fixed_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-fixed_wall-Vector.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-moveable_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-moveable_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-moveable_wall-Vector.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-tagger-Block2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-tagger-Sprite2D.png create mode 100644 docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-tagger-Vector.png create mode 100644 docs/games/Robot_Tag_4v4/index.rst create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-0.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-1.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-2.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-3.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-0.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-1.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-2.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-3.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-0.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-1.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-2.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-3.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Vector.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-moveable_wall-Block2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-moveable_wall-Sprite2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-moveable_wall-Vector.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Block2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Sprite2D.png create mode 100644 docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Vector.png create mode 100644 docs/games/Robot_Tag_8v8/index.rst create mode 100644 docs/games/img/Robot_Tag_12v12-taster.png create mode 100644 docs/games/img/Robot_Tag_4v4-taster.png create mode 100644 docs/games/img/Robot_Tag_8v8-taster.png create mode 100644 docs/reference/GDY/Environment/Observers/Vector/IncludePlayerId/index.rst create mode 100644 docs/reference/GDY/Environment/Observers/Vector/IncludeRotation/index.rst create mode 100644 docs/reference/GDY/Environment/Observers/Vector/IncludeVariables/index.rst create mode 100644 docs/reference/GDY/Environment/Observers/Vector/index.rst create mode 100644 docs/reference/GDY/Environment/Termination/End/index.rst create mode 100644 docs/rllib/intro/index.rst create mode 100644 docs/rllib/multi-agent/index.rst create mode 100644 docs/rllib/rts/index.rst create mode 100644 docs/rllib/single-agent/index.rst diff --git a/docs/conf.py b/docs/conf.py index 216887a77..27fd3bbf1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,7 @@ 'sphinx_rtd_theme', 'sphinxcontrib.images', 'sphinx.ext.autosectionlabel', + 'sphinx_copybutton', ] autosectionlabel_prefix_document=True diff --git a/docs/games/Bait/img/Bait-level-Vector-0.png b/docs/games/Bait/img/Bait-level-Vector-0.png index e0250a152ddcf52ef5be5d1e406bca44b8ab28ef..dc0f21445ff93de8cfadf8182aeba9f0afe14bf0 100644 GIT binary patch delta 145 zcmdnYxR!B(N_~l^i(^Q|t+&@U@*Ys&VR5iNm5^d=mTZ#F%%!owV!PO*CDR{lc8js_ zXAuK}Yyb9qF5Nb3?d_aM+t!ZO*4EOpqSdGCrPJF5Hg4Fk!LR)1PwRV&r|G|&=yc@J xp+md&obLas?=<~5i2Zk}xDv>SyKChA_w#RhHK9g|fq{#G!PC{xWt~$(69Bo$Lj?c; delta 151 zcmZ3>xS4T+N_~~5i(^Q|t+zK1avo6NVGRi0HN9+e-m{FdW|>!x(f9VZJ!p2|kXZa> zQ+(g1)D1we@0iu+qASxvj>|@VzRD}e&CQ*ioAhqlezmIIib;uyiMsoqezuicysxgJ z1=qS#W&$^R& BM4bQt diff --git a/docs/games/Bait/img/Bait-level-Vector-1.png b/docs/games/Bait/img/Bait-level-Vector-1.png index a515c72d1aa1f36e275610c5e5188cce8040f6cb..2cab70c88c2054632921c1caa892ccd98f5a91de 100644 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|?kOba4!+xb^mKG}j>m0f&oy zJJ!#dTbjO6yW3h~iQu~KpBLkcB%E@s)euV%4 literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLn`Qhz;N978H@y}f&ox5+`kA@JA@ z@e7i3bwkX`4A{+Qv)2Cia`=$yspi-jb8FGfy)~>7EsV_u2?A_9hd#92^S5~!u{|j} zZuwOO9=OD>)tXiM&!5awHec;1VZg!6mUsXtYm(7*uf4WJdTaJ|&GS3sRv+~BgiG!> zbPac1cl26M_LV(WnSSqfqe}Pdzx(w*>FS#5l&#MNzfQEhsEcITrrZDYc0FJCHq8`f z6VQQfBK|d(L!X4@oV8dBu^#N|37WUEKrU~9xa;g2=2I^(LbJqX> diff --git a/docs/games/Bait/img/Bait-level-Vector-2.png b/docs/games/Bait/img/Bait-level-Vector-2.png index 6def6d4efde28bce3d13741055248ab2ef0eabe8..2c6bfd83bc41faf337d1edb03261eaa708f7e0af 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfclLx;TbZ+?`c;SN`nNyt1$O)W)(8 zS*ACpK+FKDP3X#-?~!f%TzyNtVV|n+&vRkTzMeqs%xsB3yOzD^o4L7aYvmo6Uwpq8 z{H(e5eydcD7sLXfiX8_rY)i ziloh=o_W5W*}7)y)t~45H`^_ii|lIar(0j_2$}zH@6|Y+!b?4GqqZQq=lc9bsip}6 g5I+L_P*=;?d(p6`bI-qjz%XU-boFyt=akR{0LMz5;s5{u literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7#x;TbZ+Z^0oVx6v=2vyc+UwN@uTI%SDw>w|Hsk_6(bEv^l!hnOBE%8TNU4PvM zeXEZrj_oTvH?i8bmu>PSg9HIKuoAufUGw54-TIPVCs+H=H#It~?Ed(~_N44%8sG1s zC|t8h{q%}!C+Dgkn_F7+++LbjO*Qd=0*^!sW3xeo>-li|+;gS*?(TW%b1LqBl|q({ zsTaLldARb(pYXFv@1O2}RimF9U$WP&+0zr^K%gn&8h!gN?%nPFf6DsqnNvVUg5CGw ztL~p&H%=}6udz+e)-Y~*`b88=*GHO{oeJN1Y~S70wf7TEK%RxV=$Tc`;%GMnKf1xO bzLvo@!0^JZ*)DH^0n6a&>gTe~DWM4f=R28S diff --git a/docs/games/Bait/img/Bait-level-Vector-3.png b/docs/games/Bait/img/Bait-level-Vector-3.png index 44b5855686921226a89e20c12e5efc4a66a8b062..00d4ba58ebc1ff965944021b7250c6c4df56961b 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK-!2~2zp2q|;FfclLx;TbZ+^yJfOfM(Zbkl@a2N-eoHUk zytUCLm6uXa&AS>ipWXJI^|ddBoA-}Qd zdep>X3djJktidm_zvt)G+^Iga%u&LCgBfCe$uZqsSC=pLg(?EsxNtvEXkGL&1747c z1{@%%wsYHF$Zfw_{BL_%ovLrMvnSkyBz>E9JiIdM~m!uWH(Z4Of$-V!S|#3^rWdH4Iwcp(OWY;1eD?M2+~ywg`r zK;{8ef~44PFLJ+=cK&Df+iAYYhHlQk`1$YO4aIftcTILo0UHT&vcX}oz0ZVXkMl?9 zUz_*$4w_W$R$I%uc{1-$zQ59RIb3Vm!KErt=Loc2Jgxi3{+JOj#CZZh2Q0{!57bwG VQMR_6nTkptq$U(sMVCKiTS(8=g={!k!d+V$?(;~I2w;g>h>dE=6i93GqnGu^1 z7+iY(?ANoJuELFe+giY2iCo;Ws?O)z@5bNR`nLYnRepX>5O^8>TJgM15Ti{}w9ewk znKz-j8DA%c9N)GEOr+*t% zY5kmE*X+$+&wI5{yRCD(+QbPU;1|IUb;XzEpIN^Dkp1SjvL=oJ2s~Z=T-G@yGywoB CQe^=E delta 227 zcmeBR>R_6nTW`qMY{0`Bu=~|sC;u=D$uB7{?;M?aDuCnsYSDv}CHGC5V0%qsb)I32 zA{fZ7d{=7yJEWvKZ&?c%sMywAG2Zmz+~4o)=XXBe8ZIv<1Ok^FFCU(M%E<1E&h%|X zFHcXI00wM6{N9!81TXqH9qV~MUC4$s%T_UC24xt;T^WB>wBS3j3^P6gnPbQgJIeWyAmT4bldN4_K8$r5L(?F$VqA_7h+L0#8>zmvv4FO#qa8 B5C#AM delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0YQVgzWFBG%)~yr>mdKI;Vst00V&xJ^%m! delta 38 scmebCo1m+>;s5ytX#>LttjeKM3|+q%gMMoJ2`~VGr>mdKI;Vst01Rymw*UYD diff --git a/docs/games/Bait/img/Bait-tile-hole-Vector.png b/docs/games/Bait/img/Bait-tile-hole-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..a5cdcff224371d8a0d41452399d01f360857fdc9 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/Bait/img/Bait-tile-key-Vector.png b/docs/games/Bait/img/Bait-tile-key-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..0dea1ddcafcc14f793d3b3594bd9a39302c7f696 100644 GIT binary patch delta 39 ucmebCo1mlg=lq8GjOGbR2e{o;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/Bait/img/Bait-tile-wall-Vector.png b/docs/games/Bait/img/Bait-tile-wall-Vector.png index 0dea1ddcafcc14f793d3b3594bd9a39302c7f696..6985a08783e70b5bd12babc65a4dabf7d339a02c 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0gnPbQgJK!&-o4W8O;-t4sg4zGGS2jV7xTt?2If1AnSyKChA_w#RhHK9g|fq{#G!PC{xWt~$(69Bo$Lj?c; delta 151 zcmZ3>xS4T+N_~~5i(^Q|t+zK1avo6NVGRi0HN9+e-m{FdW|>!x(f9VZJ!p2|kXZa> zQ+(g1)D1we@0iu+qASxvj>|@VzRD}e&CQ*ioAhqlezmIIib;uyiMsoqezuicysxgJ z1=qS#W&$^R& BM4bQt diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-1.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-1.png index a515c72d1aa1f36e275610c5e5188cce8040f6cb..2cab70c88c2054632921c1caa892ccd98f5a91de 100644 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|?kOba4!+xb^mKG}j>m0f&oy zJJ!#dTbjO6yW3h~iQu~KpBLkcB%E@s)euV%4 literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLn`Qhz;N978H@y}f&ox5+`kA@JA@ z@e7i3bwkX`4A{+Qv)2Cia`=$yspi-jb8FGfy)~>7EsV_u2?A_9hd#92^S5~!u{|j} zZuwOO9=OD>)tXiM&!5awHec;1VZg!6mUsXtYm(7*uf4WJdTaJ|&GS3sRv+~BgiG!> zbPac1cl26M_LV(WnSSqfqe}Pdzx(w*>FS#5l&#MNzfQEhsEcITrrZDYc0FJCHq8`f z6VQQfBK|d(L!X4@oV8dBu^#N|37WUEKrU~9xa;g2=2I^(LbJqX> diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-2.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-2.png index 6def6d4efde28bce3d13741055248ab2ef0eabe8..2c6bfd83bc41faf337d1edb03261eaa708f7e0af 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfclLx;TbZ+?`c;SN`nNyt1$O)W)(8 zS*ACpK+FKDP3X#-?~!f%TzyNtVV|n+&vRkTzMeqs%xsB3yOzD^o4L7aYvmo6Uwpq8 z{H(e5eydcD7sLXfiX8_rY)i ziloh=o_W5W*}7)y)t~45H`^_ii|lIar(0j_2$}zH@6|Y+!b?4GqqZQq=lc9bsip}6 g5I+L_P*=;?d(p6`bI-qjz%XU-boFyt=akR{0LMz5;s5{u literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7#x;TbZ+Z^0oVx6v=2vyc+UwN@uTI%SDw>w|Hsk_6(bEv^l!hnOBE%8TNU4PvM zeXEZrj_oTvH?i8bmu>PSg9HIKuoAufUGw54-TIPVCs+H=H#It~?Ed(~_N44%8sG1s zC|t8h{q%}!C+Dgkn_F7+++LbjO*Qd=0*^!sW3xeo>-li|+;gS*?(TW%b1LqBl|q({ zsTaLldARb(pYXFv@1O2}RimF9U$WP&+0zr^K%gn&8h!gN?%nPFf6DsqnNvVUg5CGw ztL~p&H%=}6udz+e)-Y~*`b88=*GHO{oeJN1Y~S70wf7TEK%RxV=$Tc`;%GMnKf1xO bzLvo@!0^JZ*)DH^0n6a&>gTe~DWM4f=R28S diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-3.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-level-Vector-3.png index 44b5855686921226a89e20c12e5efc4a66a8b062..00d4ba58ebc1ff965944021b7250c6c4df56961b 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK-!2~2zp2q|;FfclLx;TbZ+^yJfOfM(Zbkl@a2N-eoHUk zytUCLm6uXa&AS>ipWXJI^|ddBoA-}Qd zdep>X3djJktidm_zvt)G+^Iga%u&LCgBfCe$uZqsSC=pLg(?EsxNtvEXkGL&1747c z1{@%%wsYHF$Zfw_{BL_%ovLrMvnSkyBz>E9JiIdM~m!uWH(Z4Of$-V!S|#3^rWdH4Iwcp(OWY;1eD?M2+~ywg`r zK;{8ef~44PFLJ+=cK&Df+iAYYhHlQk`1$YO4aIftcTILo0UHT&vcX}oz0ZVXkMl?9 zUz_*$4w_W$R$I%uc{1-$zQ59RIb3Vm!KErt=Loc2Jgxi3{+JOj#CZZh2Q0{!57bwG VQMR_6nTkptq$U(sMVCKiTS(8=g={!k!d+V$?(;~I2w;g>h>dE=6i93GqnGu^1 z7+iY(?ANoJuELFe+giY2iCo;Ws?O)z@5bNR`nLYnRepX>5O^8>TJgM15Ti{}w9ewk znKz-j8DA%c9N)GEOr+*t% zY5kmE*X+$+&wI5{yRCD(+QbPU;1|IUb;XzEpIN^Dkp1SjvL=oJ2s~Z=T-G@yGywoB CQe^=E delta 227 zcmeBR>R_6nTW`qMY{0`Bu=~|sC;u=D$uB7{?;M?aDuCnsYSDv}CHGC5V0%qsb)I32 zA{fZ7d{=7yJEWvKZ&?c%sMywAG2Zmz+~4o)=XXBe8ZIv<1Ok^FFCU(M%E<1E&h%|X zFHcXI00wM6{N9!81TXqH9qV~MUC4$s%T_UC24xt;T^WB>wBS3j3^P6gnPbQgJIeWyAmT4bldN4_K8$r5L(?F$VqA_7h+L0#8>zmvv4FO#qa8 B5C#AM delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0YQVgzWFBG%)~yr>mdKI;Vst00V&xJ^%m! delta 38 scmebCo1m+>;s5ytX#>LttjeKM3|+q%gMMoJ2`~VGr>mdKI;Vst01Rymw*UYD diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-hole-Vector.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-hole-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..a5cdcff224371d8a0d41452399d01f360857fdc9 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-key-Vector.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-key-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..0dea1ddcafcc14f793d3b3594bd9a39302c7f696 100644 GIT binary patch delta 39 ucmebCo1mlg=lq8GjOGbR2e{o;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-wall-Vector.png b/docs/games/Bait_With_Keys/img/Bait_With_Keys-tile-wall-Vector.png index 0dea1ddcafcc14f793d3b3594bd9a39302c7f696..6985a08783e70b5bd12babc65a4dabf7d339a02c 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0gnPbQgJK!&-o4W8O;-t4sg4zGGS2jV7xTt?2If1An$V%n}Pz3BPJ!-uQl?wYR*_6z%S#!bawD>Kj>gjoTP4ov0k zD|@gwq|H|Pz!g4_X0QPuMUIWH_t;lnyVbhy$xgY_-O6vaH>nhdwF+|jyhRNHpQ7FA z&b_Ao+7Gg~-@QLKG|`1A*cuW#5cjmOOuqBxX2C74<{$gNJ-%%2SN(R@{X=`cn<_P; oJ6ovx-Ax8Jl)wTxC@R~Z_o~n3^9Sxa0Hck;)78&qol`;+0NW|6k^lez literal 477 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GsLJY5_^DsH{K8@TR}f`G%t zzL)>D+q`2CDm1z<@AL0V>D~9HxGb4i$=Gez#?Zpz#H7dw#ElaiJQ^k}IA;5^`p>`h z$ve`Nl8-;suz9{l>iJF4rysf=?MZHL77f1|H234{UCNCsWjQ!oSU?!8ze#B#_x&C7 zca$|ge)@wsJC%9Ht;PuqP(vL&6jUmvR6l)SQ{dh{buY^=liVK-)P%=kh=-wk;3*GI#p+XBXwZ zui+|JG1$rs^bN#E3MvjB0V&(k51$T`@TuD>|M14y`>ltrZ%qgL8WfBm9}5U6sJz(l mdLHkCK4cew!uddHEu+?x;H%l&c~XGU#^CAd=d#Wzp$P!P@xNjK diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-1.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-1.png index 69599dfe86b363ad7de525e330b82c27f2c88d48..c1fa2fb62f7e80e19b12b953a2def6feb5da6a9f 100644 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJzX3_DsH{Kd$H@Vf`G%t zGn4-RPyM|wK=+h^+Pu%Za&FIv<2iC{N7U{H)A}}s78WNaMMfZQoZ#TmFhSw?-T%ei zGwNPSteN>@f06gE5Py-i^?mCOyDffJeYHN`SZK!~AJ2AUnqH=$A|M399GopI z9GsIx*5yR(-2Qh@D3kVXm6U(^-%FL_%EdOFn<&W{a|_}?2M>rI52G8si|W^J-d+-&Xs Vi1Vtr-r>mdKI;Vst0LFy0 A(*OVf diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-2.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-2.png index 04921d5c297b11d53582a10ec96e3b83f2e6f7ea..8472b11df36c54b901ffafee8c401f9d1b290365 100644 GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GuRJzX3_DsH{Kdob&;fq-k^ z>L>r}D|czf`Yt%UddasrxgE{2@hKZ7TnqlnU@Wi4$idmd;=}~Pij5N(6djMP{=ef| zQE%V%^S&SX>voG>z5n)a`RNW8xwj4;3MwEhAjH8bAoOQn_`3_)my-mZhT7ThGFK~K zBgkoUt8s#ZM*|RpR4J%%aGuLKvDbB9@w-E{+S+Sw_^GC^V>Q~D-0Sh+A|I-?9wlen z-i3ac`#Z)bP5sF^^U{>+i4qfG27#<>Vd3E1R&BgF@p_+-@z$3;McOh;{w>yit|zne zN2?&%7mAEvUx1vT@Z{{7wv+q#f2X;wxga_BXVBh%w-0f-&s&eFQe}s<>wdWdIZGR3 zr)PgUTzYrn@4qfZ-`ha`ff)!3r+qBxW>2o=yy&laCB^oA@9F>5pZSZhdcAYq%?F=~ zrk$KK13E9ocB3r&fexUbz(Eg_IdDP!)3VJfXCtD zt{?xx%gS5xQo00+CVk)5I;G-Qr{W>wYgLS%`Q8jpOp1*Y7=YNpLqWyCBOp1?PJY^p z66HC*k^h?Pv$r1F$|as%snobumV>i}1%!b*7@eBfHov>H_@zb1+25IRI{$KWp2;0D zl=oNJu$39ryd?#j6hDhu7C$wP{gl$W>*5sU_t$6gCShTfTtF>=cmmB)Y z%>3vAQwQ>ifRKWU#-lTB`^B{P#HO3wnQbh6;2IyuM!2nx?U5C4Kls+aGU>Qz>@s0y z?26f48gsTs*zV^A`va;_hY{lGeO&6&Mw_{3YL}n2J)&2+=Sz~gXH zqV#{XnYBmb36uWfeHT}76;Jgt&D?T^t*0`QNkE8$vxNnOotP9EotmVs-(^pF(EmM6 z&FIjp%@banW4Jl-z*<>UMM~3EA8`Ji>~w8*)kfvU2@W0#Dj*CpOF&4(@aBi9cjq0q zPv5?E6Q=CWnK>ukF~9it^N)4atIC>Pt4@X=?gLrgIDr9(A!Y@nyg1X=BUxW5e{=sc z(f!LT-mJQ2Esagx;q8HEtad-}6`SAvH0%1XzHISkv& z8o>4l2tnM;m$WI7Yx6u~!^eLdQ*MF03lWEgOH=V0%bO35^0A-(@t3=A&9Cj7l^dh2 xA)bWa$c3nC=E@67A06JW{soL622WQ%mvv4FO#sszrq}=g literal 444 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gt&o-U3d6}R5rjqYnU;Bg3y z+4x^~w{?MhpaJLeKVh>Hoz@F+W_O%nmiV%XML|VCh=UV^TUeZ!T3Cwbd_7+H$Nt9z z_bc{NVUx;K3brsSHcnswVh0Ze6$g(C54Nzx=%3PTj460^v=68UMb@D6WSjh(?>75R z*JY==gXA1M8o)XQgcMX}sBEgc>++Yy%Dry$(f|L{rkX3xD-F$E{V-SLK?Xa}C`Pbx zAT0|H-$*&!R3FqfvFrE!gNyex`%X{Y#3=I?;s}_lOKucwdbo}|w)T~;826ebC)qGu zdoblN)Aoh0|NHcu5y-JPnTW*hWl_4Ao3J#^d!$9I58n+u4RbsmT;gkCabf~tuv*8-r#b2p${E##I5<%xV1_8D2mqx(xP=9xM3E7! z*TJI!Xpcec8lT`Dv2I`8#UEN1DIr@A(rI&b`q{Q$;^h?qTB(|TDg~MBK)ne43r?0k zyS?sxqJVPz_V>2;O?#`4oQRddaEs$)XLIRC2d?+-pY3-0dFJidjv`JKbGDZ*Ow-Fi zwm{qna^I|;yEzsg`Pk3zDnHaW_ipRlw=Frph2{W*1K|sh?|qNunYE#^ddgSE@MeIT+X&Y1;7CTgbHpOHJFR?|T$uXHP;C&iDBJU5$DcmS z#gG55-Tf?d^~N(_ImNo~t?vZ78|*rWDGnY1Dx02uKVlPgGyT)A$MXd1h>=iHaqx$gA2*W iO-hU77c*cAm)d1TYWzsGTo(e27zR&QKbLh*2~7ZP#gPU8 diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-5.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-5.png index 520e7f4c2a0d6a902c6c65dc0ded8c277ac16d4a..2fe92561faad22487a4547423d8140009c75e477 100644 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJzX3_DsH{K8<^K@AmHGb zfAsJAH}?{BHx^!-F!O%bGRAouTC$JNC}uFN_haPXY+-R?0%66*2@Hw{<--3xmJIyy z^yi#Ti%%_Ye$7^%>lkEx{^#>&*S}XD|8_V0vM?vlTL%vX6%ZB>;@}hzs)_HpB>nyO z`x(l%WpY;U8=3QNW~MPq++qY9>fq5Z0i^baV))w+zw;;V{&rx+8r2KWI=ka5lu(s8 zczodOHkJM{rS`XP&R)w+`Cks0S}rQt9KD$l-AgKZM;9h%YlyR8UYo6RJ*VRS8MbKa0_~}pXW0z4FoXTt2zHi($At&K_O9QP{mw~d z=X&@1dJ^YnI|kiceF)Q^Ih$fOFLb~E`$w<(uID~-R_zIP?_ObX+m?tmH$Uu?WZU^? z|F_R~&BKnnoqpbT<7@5tJzT%-u49_J#iHaa+XojWMbywp==sR6`6Te>^m_XQVC*t@ My85}Sb4q9e0A_{GQ2+n{ literal 515 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJY5_^DsH{K8`!tlK)}KA zd&lqiozGb|F11*DEa2`@|M>TR zt7X}0M(-?STA$AeeZ1$Fo8@1(!h7k7U%`SrU4 z#bxeH!PX#OL44-K)WRaWZhP&+8FqX9yiM!OF7#VJFTqs98@GMa!h*MlZ12CmxaH7; zBcdvwd42ZuGWom(xd`GakeOQ|_Vg}#zempef-krHr+Lmlca(?jP;Nx`qta?y>9}uY zZAatZy32gO`BQ&K@tx{_T&>5qrZdmD1qm{^R|~G&N*|C#js{R@G#LJ4G)|7Zc5lAO R8erryc)I$ztaD0e0sv=a#?=4- diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-6.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-6.png index 421f4bf1394002cd8567fc23646a78a00f56057b..831eb2ee18720edff3eabfdb8d6df93a659e72b3 100644 GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gr!JzX3_DsH{K>zQ}hK)}_p zbiwcVnUU$vOnLPIA*;5vvGei^Da=}v-NXIj=SFS^4+RwgArR)^Y+>QxG0s|0(lsR|^6utlb>gE3D$^HwT|KH<(j_u;i%mo|w_Wq68 zIMr*O)$3!=Z@=zh$$JY?4RMZuP)m-?%?p*=_NGs9D5_4@)-m~ZEbGs~PItx9o|uUT z*2+Sh+yb(U(Wz;|u4B*7FTcOt>rm3PrkCIP-0e=U_bpDIQ~Bljdg+@C=!WNPkhpmv z(sY4T@o)RDoxhifswS;=(%JT{G`sjoUz4`bj;+k7UJ#1uP1;kiw^QA&(7y0&$2tAF zXJR{*U_L~+dG_XqCrSm*&CY0ezclF_TR}EE*nOY?0fk#a`_VbPb4693<*z+9d-7_P zn7Q(Ef8BaHPiE@vJL-MDk!#NeMxWCTS3%dOkYbq5`~V|lH(b;|lNbK5yY0nNU>acX MboFyt=akR{0Hf68%>V!Z literal 528 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gt?JzX3_DsH{K>zH@gfQRAW zh7bSupZVutvw`Jl$+IYvzPUOo2fAct$}f0%T$Mqwae{+K0}v~y2nZ>t%=jo$-+wIo z`OntRKh_(5T^Fa(ciP|R-_55te#I>n>Z&d|Y;|D!;|*tmIbQLa~=-k&(hi3hImLA(xj0fXYf l30dda4s-xR12rh0iwjL>r}D|czf`Yt%UddasrxgE{2@hKZ7TnqlnU@Wi4$idmd;=}~Pij5N(6djMP{=ef| zQE%V%^S&SX>voG>z5n)a`RNW8xwj4;3MwEhAjH8bAoOQn_`3_)my-mZhT7ThGFK~K zBgkoUt8s#ZM*|RpR4J%%aGuLKvDbB9@w-E{+S+Sw_^GC^V>Q~D-0Sh+A|I-?9wlen z-i3ac`#Z)bP5sF^^U{>+i4qfG27#<>Vd3E1R&BgF@p_+-@z$3;McOh;{w>yit|zne zN2?&%7mAEvUx1vT@Z{{7wv+q#f2X;wxga_BXVBh%w-0f-&s&eFQe}s<>wdWdIZGR3 zr)PgUTzYrn@4qfZ-`ha`ff)!3r+qBxW>2o=yy&laCB^oA@9F>5pZSZhdcAYq%?F=~ zrk$KK13E9ocB3r&fexUbz(Eg_IdDP!)3VJfXCtD zt{?xx%gS5xQo00+CVk)5I;G-Qr{W>wYgLS%`Q8jpOp1*Y7=YNpLqWyCBOp1?PJY^p z66HC*k^h?Pv$r1F$|as%snobumV>i}1%!b*7@eBfHov>H_@zb1+25IRI{$KWp2;0D zl=oNJu$39ryd?#j6hDhu7C$wP{gl$W>*5sU_t$6gCShTfTtF>=cmmB)Y z%>3vAQwQ>ifRKWU#-lTB`^B{P#HO3wnQbh6;2IyuM!2nx?U5C4Kls+aGU>Qz>@s0y z?26f48gsTs*zV^A`va;_hY{lGeO&6&Mw_{3YL}n2J)&2+=^*>?+ahSWwSJBZ)VRK#yOuiu_&kr2yt+Na0`nQQwxh}-&Oe|o&599 z`lPg6eeRZI#hl{Kw7m?Z45GA!g@dz8W?DkN;e36`@UzpOJX-bEHBy{-IHS{AKs4L2Pq)i{Ac(Q)F@6lXtqhx6y3-=8SVXB88A{OShf#t9A{3MwEB zagvB-X_~X!@&9}J=fr=O-M_5jM(Ex5IXqZQqY#lrC?a`iC6dV@%bNWqZ z_4Q)4b7usuoRzoO%y{P(BiJ<#9t~i7D+DI<>Xz81FM9mPG3D0818Zdw{sjh2)AJ6) zTE96LKTX^%ZvEl*dE3=>Wo0S_+3XdCvB*GPo$r1jjrzX`@rhFKVtQowe$ZFJ6>%t$ypXEG)*ntWG;TY5$&n zo%{O*e%tCLoK=_ElUsJM59AaSS08Q6UmQ_fc5t@NyqJT3!!BW}IFZWv@A11Wbw{83 zd^sb~Gu!^d8McCKcDTPl20jRVrTo?PzWKps?mMSucFO7(fc=l~7tjYy;x|h+9kgIm z-uLcnx!xO(2iN#O{sQ|A{P53TzvEr9XF;OXk;vd$@?2>>Qy BrN00G diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-9.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-level-Vector-9.png index 63cb3d3e7a60a9e91db7cf8d56ee945cfcacd6ea..053dfb1367b224b9758ddb185ba01af67235cd8d 100644 GIT binary patch literal 505 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|D>#^d(xr%*+&4a_b2@k^s0avwFb8J~3kPSF&DZ0S7fT*Q zTy=3>x1(_Dr{kK2zkgjz4>`whbK-%uvJh1*EKW>{j808z=T|cO*KD)>tUiCH!HeI| ztkc!rfDPo}gc#_=)WYIB@B7@K`;*^yzE?bPeXrM}bHh#G1Jq-2+6WFYV2@6hMIN2sqai`*L)|R>c zx8FETaaP#5aYC6&K{h*@!mX2eh3^-9SX-|C=Els}8#Db*Z0k;NhZzKOd`8jR6z3n$ zF24J7p2Hd%z;HVcT5L30%X9;aK8J(-?QD}udUx(eqfX`c)I$ztaD0e0stNR BzPA7X literal 490 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|D>#^d-e-Tb2jo|f%RzosP;u~>kn+WhYdPzOJzMUl{C|E=DKCDiB&W_>h@^rF#5XFD zU(U42`dd`oy~$~;_D%218$XFRu?EVGAon6X>(umBXV+%MeE9=?^_AZG*4#(1TXz6M cIN&+^LKdeN_gAOP2gV_Tr>mdKI;Vst01xJ^;s5{u diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-butterfly-Vector.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-butterfly-Vector.png index c0ac2796817cbb2ec5f405e43f3a93bb6196fe93..758055b8f4658f1313667b0074ee7ebc3484227c 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-cocoon-Vector.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-cocoon-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..c0ac2796817cbb2ec5f405e43f3a93bb6196fe93 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- diff --git a/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-wall-Vector.png b/docs/games/Butterflies_and_Spiders/img/Butterflies_and_Spiders-tile-wall-Vector.png index c23edfdd306daf6343a992c6f6f5f7ee99bfe1f0..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- delta 46 zcmebCouHyA;_2cTQgJK!&;R+1{0T`35>t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy diff --git a/docs/games/Butterflies_and_Spiders/index.rst b/docs/games/Butterflies_and_Spiders/index.rst index c23443524..da2a51b25 100644 --- a/docs/games/Butterflies_and_Spiders/index.rst +++ b/docs/games/Butterflies_and_Spiders/index.rst @@ -1,6 +1,12 @@ +.. _doc_butterflies_and_spiders + Butterflies and Spiders ======================= +.. code-block:: + + Single-Player/GVGAI/butterflies.yaml + Description ------------- @@ -132,6 +138,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Clusters/img/Clusters-level-Vector-0.png b/docs/games/Clusters/img/Clusters-level-Vector-0.png index 80ba13c34aadb7bae9251793b758d2865dbbae14..596bbaf07ad552ab3160729b87dbc1a8ca91209b 100644 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFff{Ux;TbZ+-(&`A8#$a+P-W0ESuUX8DFAyA6%%yBhkVL z(%&X5^3#0R?z^t*Yt!Dealh5Og(UZJcGcdG+Iz3hoo?|UX=itt1}8IH;sFJq-h(1{ zm+$;Cb8_-&wTD&RZpEAKJ_rzkxW#~@S#ww9*?kx1o)crd;Y1MjqcCjm-%zqoIT+> zWv4vW-xYn=^|*A!UfJDo?-ubQw3l4C@Tz?}_q&rQqBj=ZHZi+3dHDyw3cmjvX~%NIrG6(T2?4{5!PC{xWt~$(69A(ti9P@T diff --git a/docs/games/Clusters/img/Clusters-level-Vector-1.png b/docs/games/Clusters/img/Clusters-level-Vector-1.png index 353e977c8c40a253795c4eb3180e514594fc1616..67d68b4f64d64b614d819ea10d79f5abe406609e 100644 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7!x;TbZ+AXFFSoh;o1Hx+3^^E>VFZEx4Z`=M4TdzEMwb@BU-I%l+-5|2+KrmaFv?g!zBYExu=QUwymJ=ImX$ z>n<9om)~AjEhXc1sKF6plZA`*UDxl@5pNf@!W`A$IQ#XE?sMgb=GP_fsr7re{pq&u zG9*jS17-7W{_49WvHm5C-PE@G$+wa1Oz4s;zFg}0zVhBv+u4g`5mx>9su?v8=xMOk ezyP?gTRd9Ow&ug*+4jI-W$<+Mb6Mw<&;$UaW}gNC literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7#x;TbZ+A-MZUj zw>W!3Gy$C`o_{#D%;y%tPS_x@L`K~Z|{i=`-N+Ne-cd)VB*`)wMiqV)y1DVq|4*-SzrOanI_dGfjZbvmhuz@iQ%yXez$4MZ z*lZxPXw7`r?>-leT&<^o_y~iT*&vD@I!FI-Ew{VHCw_Nb9AVGkQ=g@}M zzOT>hZk7Liw~BG^`&;F*mSWMy=iXM!tx7(Z{@ZZ><+}-iLO^v5juHkOho5!-iu^K1 zGiq5z_aumZusAbY;sFI7u%Ul4h4V||!+x%RS)KEKM*cfCB!y-6uJ^^O7XL1v{2?g) zNpBvK)Hi!q|1-Ouem^W%skJT5CcMH@?c9oADqFEEdof^hBowAxsmV}CA&?7Y-H z)%o*#rg^T>2+gTEwQtvA}Y?&AdCi(;y9SG}q4nzkF}WuQyHyk$Oh!e~$R#0}qo P!O7t1>gTe~DWM4flVq8% diff --git a/docs/games/Clusters/img/Clusters-level-Vector-3.png b/docs/games/Clusters/img/Clusters-level-Vector-3.png index acf4b8138d45417f0b03839e441ffce317c5c258..7e6617b0c8bcedc8d63b99df9b514ef09bf1ac91 100644 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfjUgx;TbZ+CRasUFNxd-sbMwl_#$*Jo0At ztIgti&Jnz3s)+{_Aj zzt8N&R|9@joUhB*O*FkR1!4zK<%jdSC1SGMt$wFxPoG?=N5+H=+i)%oRpKeF{|0>YB!;4goQKdnFaywZ7R6v$~%Z3Zu5SN|#7xMKU4 wdAnWjZT2s@jj(gW*WfK{V6FuE^}=fPKZgw40=m;xfC0?l>FVdQ&MBb@0N)CsUjP6A literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfiJCx;TbZ+;?)1~^mEqw#kcg=`63%U zW6|}M_iE%)y_UbYC-OExh>hn^1IXBB&8qa^l4Ae)_qOdw3EqO_pX2ivrJ4f`1uFzv caBnYzx|_|DsINJ5fFa7@>FVdQ&MBb@04>#%(*OVf diff --git a/docs/games/Clusters/img/Clusters-level-Vector-4.png b/docs/games/Clusters/img/Clusters-level-Vector-4.png index 4d8b131566159775bb3edd5f3ef82ffbe320699e..89b49a01bd5eccf48091bc7d8d3aae4a4df4eca5 100644 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfclKx;TbZ+YjC9V0ETwPyyY+ui;jkDKx-$YpdVSV+dQo~4I fsJDUEE^sgJzoeO>$hfl?7^n=Mu6{1-oD!MEsI6|1U$=u+5%i0Fyaq=lcDIO$zgCqn4`*JQ6L8&0wjlysmkdFJFJ!o@jPz zR*bG7uifUW3Bf`z(++cX{VBh=b>EA3A8jnpi{CXGV}9{d1mrI*8Z5{_wV-i?PjjGr@(AJRM52Q@9qdY_4jj< zj|U#EwNsZb@5?p4flzS4wfCQ1)pGZ$ulF7)TX%2MLs>gTe~DWM4f$C{PL diff --git a/docs/games/Clusters/img/Clusters-tile-avatar-Vector.png b/docs/games/Clusters/img/Clusters-tile-avatar-Vector.png index 449dbd771182fef12e06a8a184c42c601fc0d169..bc5c2b3ee088996e95dd51d51f5d9ef337bf7373 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VWXjOGbR2e{ozmvv4FO#p!v B4;{GgJzmL73@;{GgJzmL73@gnPbQgJJ}C-KMm25AGs2dv7WQVi>?81qkeYAj*^0#8>zmvv4FO#qEC B5A^^5 diff --git a/docs/games/Clusters/img/Clusters-tile-green_block-Vector.png b/docs/games/Clusters/img/Clusters-tile-green_block-Vector.png index c59c09c03c6c4a719238e15ae4653fccfbc1853a..1ea637a7b36f451fd81fe5aae10ef8514b6d8c15 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJJ}=jZtbb^}9$9gnPbQgJIe<-+gt4bldN4_K8$r5MWE7#+Q(baEJgz|+;wWt~$(699w1 B4+H=J diff --git a/docs/games/Clusters/img/Clusters-tile-green_box-Vector.png b/docs/games/Clusters/img/Clusters-tile-green_box-Vector.png index bdb0ce86ae6361287f80a61213cd408e7d47d1f5..cddd4d04ef230e1c5b98578b3937de5fcf9273db 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJJ}=HGlq{)D6iiK)j}7{d7(f5k^sI5Gf%r>mdKI;Vst0Bcze A=>Px# delta 47 zcmebEo1mg4>gnPbQgJIe<;KtXjOGbR2e{ognPbQgJIe<;KtXjOGbR2e{ognPbQgJIe<;VKt4bldN4_K8$r5MV?868vhEAlY_fv2mV%Q~loCIEop B4*viE delta 46 zcmebCouHyA;_2cTQgJJ}=HGlq{)D6iiK)j}7{d7(f5k^sI5Gf%r>mdKI;Vst0Bcze A=>Px# diff --git a/docs/games/Clusters/index.rst b/docs/games/Clusters/index.rst index e54806163..61f4d6a62 100644 --- a/docs/games/Clusters/index.rst +++ b/docs/games/Clusters/index.rst @@ -1,6 +1,12 @@ +.. _doc_clusters + Clusters ======== +.. code-block:: + + Single-Player/GVGAI/clusters.yaml + Description ------------- @@ -84,6 +90,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-0.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-0.png index 730e31b78749c9ff206edae7b70ed535377d7059..8c6293db779c24739f59b3510141df44b8a18a3e 100644 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfgimx;TbZ+Z#dVcbhs&H3hwcz@end-vDxswBOe(z2>P zlV1BF6s&HOZkH?Ps(f3(U; z1E3wA6W~^YR6t#D$yIlMVZ{>v>;L$&&;7I5B$GHPrX1>Opb_Vy@?@@Es1XW>K7O|ZTWTKfvqp2ierrYgOtI$# z1Z#dVcbhs&H3hwcz@end-vDxswBOe(z2>P zlV1BF6s&HOZkH?Ps(f3(U; z1E3wA6W~^YR6t#D$yIlMVZ{>v>;L$&&;7I5B$GHPrX1>Opb_Vy@?@@Es1XW>K7O|ZTWTKfvqp2ierrYgOtI$# z1}}BeZo%a%^84i7E;~HlxnIS_B2!q)dsj_N*(D5C0W#dgR_(bqpY6NNGt!@X%zOuP zCs02~7RG(GsqMGL6z{4pzy5Ykuku&yd-+=-S;BLfHPppGyDmF_vD&|C$I|(}bL97} ungDbeRI^7^DQ|h-l!-8(0NwiQ_Ps7G!@&6kdw75W%;4$j=d#Wzp$Py+tD2nv literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfh7#x;TbZ+%OhI8nsz zIe~?95=i&d_1fhxB!bgSVi)b~7IpsCGcQps@*Plv0#F`k^x4@N=Pj<(yQNKf>IV~Z z%vz`MHX0jE)+uiSngg{5mdKI;Vst0M%2MIsgCw diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-3.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-3.png index 6785f507e6e5def2a55ffd2a66c4ecc83063959a..746adf189bf7adbf70fc244639fc23f398957247 100644 GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfhh>x;TbZ+oblc_ac0U+VHJ-i6^{ulo)b=)uKjUq`j=O0?Jqf=cAB&OABy!=$RWsUd${+fdC5 z{Up=AXNLdHr^{zTYyg{d>DTt>$^XKSR$0pie%Nn$253LfEU+tsc81j)Q}lbe_wUy8 z;_tp*Ic6){^ZBwR-!yBW{SalD!C$Q{KTVFi+iwxkUUL1x)`S&vWHN!iaD@88?An#r zy^rU21L1^BzH{fzeYxNKMSPQQ~$J(nrdYs%Lp2d#IrPGI4jq`)bp z;wZEv$Jf3uzRl*f=|ju+JCu}9mZ*D7VDX%w093FvJL~)9z-Ol}3VH9EExJBv_o8SC z&t=w*LMk3jDj-$&uQMHw)}LUQE#p%T76F-hY2&Z0#}?kz_a78XUGeOnJc{B6eW0tDN#W_wiPGHI0vg}XM->!#kA}{4m*||@-zj*j=MLTQtp`b}WroTR`hq~iF?zVvqCiSHtdZbtU&)?3!wC!L<);;)^!j2oJ)Rjx%C=M1}X%N_f_`d_+ks9O$J1~T~4 z!K|P4GSB>ye|eVk=B+&!6LNpmhC4tRN1zvgs;a&{lVwLZ1Sq=p1*4+1L1D0bSUxb& O89ZJ6T-G@yGywoP8>Scl literal 393 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfayqx;TbZ+<;u_K{)K1id?!8jo1nlsNdai8W7fKTm%f`WF!Y?j0u%vTwWRGe z|BmI+Wnc3q!NpWOZtb;wZ!zUp>XC#=Knr1x(z-LRT&C{Flm91dTQ=^_Q$rZjv}D!o zk0&QiTy`d_>*w$3dSBjlp0?cn>rUN{N%Lf0BDuXUbnXAui+=z6TsdbtQ0>N@vNKhV u&jmXU=z*pyG4J15oKS%U0?_U+mHN-msdUc{_J0WsVg^rFKbLh*2~7a^PoahY diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-5.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-level-Vector-5.png index 1ac633090896a3941031023db16d4e0b24d0bde6..07ee11cff2f6abb03125ee3a829d5e7cc10f2255 100644 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfitNx;TbZ+dfXjlKllwHCq9!)A96IeVaw5=}P z(;fHdU8m~(#`ndRpSZl^#49Y1U*0S2xy;&82&(R-LulQqq|u6mpI&X=4b$|phTeYN{rm3x=l33;>(2fY zxb^b>94V0LK>Z*!ESWoQb=dFH`{+3#I?8Ovw22UxfwVdXeQ%2?Jdw?1diS?oFH||$ z#)7Rs{WAY{+?Kf4qXIXGbJ8yJhh2xKnYOP8a5!hc#*ncth X*z3P+=R08r41WesS3j3^P6O= zw3JPZrzp;7RNdq}{RK~KlcWSwS^~#s_ImxS6|80F{%++`bw3}p*2HBeU)5*37$%P< z7S9PRoRbtdgR*l>4_`;*R z!k2&fi%fshv9zYubJJa*q@$3EW0Q)iv^IJc#;UL*p+TShy z#9v)E6{G>k7gF&6YB+1U@6Fuh*Y~{5|5egyd2K%T43*n+6=3p9s*}6-mrM!X6qPi8 z&AG%$V248Vu8Nzi&i8YsQ~m$5i9pvQ8Tjq{#7CbeteIvp3+yDQ2eYm}>^vNt&h>TP z;><}ognPbQgJIe<;VNu4bldN4_K8$r5NUJXG~1&Z#%#M1fH&bF6*2UngF1w B5Xk@l delta 46 zcmebCouHyA;_2cTQgJK!PyDF{b^}9$9zopr0E*2J AxBvhE diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-boiling_water-Vector.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-boiling_water-Vector.png index b777829b082c9dc6ce82bcaff0b0e2a7429a52ef..3b12302023c538451d858319d02af373c79e542c 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PyDF{b^}9$9zopr0E*2J AxBvhE delta 47 zcmebEo1mg4>gnPbQgJIe<;VNu4bldN4_K8$r5NUJXG~1&Z#%#M1fH&bF6*2UngF1w B5Xk@l diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-lock-Vector.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-lock-Vector.png index 34f07f038fa615ce17cae226d1e99d44efa4e0b5..81269459b38b16ce85e5ccbff1159e393735adcc 100644 GIT binary patch delta 38 scmebEouH%m=l?85{)D6iiK)j}7^1&3{_6`rTEzeap00i_>zopr03fOk9RL6T delta 38 scmebEouH!_^8b7TyMduWk5@7iL%su})ybz--V8wC>FVdQ&MBb@0QbZUJOBUy diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-raw_pasta-Vector.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-raw_pasta-Vector.png index 0efac0d902f1e12d2a33b5b665b319814988f7c7..fd002494ae89d2b695b130d855111239cb7a939c 100644 GIT binary patch delta 38 tcmebCo1m-s;rIClX#>LttjeKM40G8S6Zg$|{G0&@JYD@<);T3K0RS3|4Z{Ec delta 38 scmebCo1m-sqrQdFJR#`-x7#Wc25k?<>;Fz0Jz)R>Pgg&ebxsLQ01WXBdjJ3c diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-tomato-Vector.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-tomato-Vector.png index ebe2993c51e189337f6bfa4c2d3a16ed25b6ec68..55f2af5df80a80919a29049e6eb10329c908945d 100644 GIT binary patch delta 39 tcmebCo1mi^T`ZA;OXk;vd$@?2>|<(3(Eii delta 39 tcmebCo1mkVV)1D{qj^Ho0dBWdCJfrVjMozmvv4FO#lT747dOQ diff --git a/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-tuna-Vector.png b/docs/games/Cook_Me_Pasta/img/Cook_Me_Pasta-tile-tuna-Vector.png index fd002494ae89d2b695b130d855111239cb7a939c..34f07f038fa615ce17cae226d1e99d44efa4e0b5 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJIegnPbQgJIe<-_mu4bldN4_K8$r5NV2F(&St^Y}Rf5O})!xvXgnPbQgJIe#p2U^M)QQE1Ke(_Oc=Cz8LuxWbUn`i1fH&bF6*2UngD)Q B52FA8 delta 46 zcmebCouHyA;_2cTQgJK!&;MDB{0T`35>t<{FhqZ6{MQ$Lw2A=;JYD@<);T3K0RWdK B5eWbQ diff --git a/docs/games/Cook_Me_Pasta/index.rst b/docs/games/Cook_Me_Pasta/index.rst index 8aff5f574..f10fe2801 100644 --- a/docs/games/Cook_Me_Pasta/index.rst +++ b/docs/games/Cook_Me_Pasta/index.rst @@ -1,6 +1,12 @@ +.. _doc_cook_me_pasta + Cook Me Pasta ============= +.. code-block:: + + Single-Player/GVGAI/cookmepasta.yaml + Description ------------- @@ -93,6 +99,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Doggo/img/Doggo-level-Vector-0.png b/docs/games/Doggo/img/Doggo-level-Vector-0.png index af4803a4510cc1a69aef64466d6e21953d184ba1..69d28b66fd8f79727f10c86e9e37e1d24ab292e6 100644 GIT binary patch delta 142 zcmZ3&xQcOtN`0ZHi(^Q|t+%%}avm_?VR6u`cwbSR${gyMbGUZ(p>PhlW}{V)w!{=B zELaEx-_N}`UY=q+_ot}#?RneeE`Y%MaR2}AYtESJ^m6bbz~A~~tJ>42d~Ik0f_v&O gH?c{9fR&Qm?^NE;Rn}gT3_#$??&{~V&MBb@0PY_=A^-pY delta 136 zcmZ3*xP)t{0mZE_uA$;BQFxDUAjyC@zRALa9>Z&K6?R}IeCu9 XQK@(G4@_AK4q_x{}h@?@S3W=tv7W8oBe) z4h2OJcspgczWqes(kHW)ZJt|^W1|8BOY-?2{d>32s66M^mxhUjB1SDD^)=r&Xepo4 m0&7xyJM+tPCQdMT8LC!#j*nfC_k_FZo6IRfEz#K0B(|RBbrFYyQ(N z2PZK2rT0C_KK*Hu%Ql&+Dc$WYAW)K3{o)n-GB6vHDDm5rwaJGQ3|4-g`RzFqgq^9a Z&s8if$kZ$Da)JQ}JYD@<);T3K0RY~9Kj;7e diff --git a/docs/games/Doggo/img/Doggo-level-Vector-2.png b/docs/games/Doggo/img/Doggo-level-Vector-2.png index f2eb7c5ff95b660a834ac8852473db50cb9a583c..0b096e36b08cf8bac6843a14947323650b42192a 100644 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51SA=YQ-1-eiJmTwAr-gY-r2}|K!JzF!8+;v zhMk)mcdID8i#DILpkk-OVlBT@qV22_VDMvqPhE|>+2xtqlQzBWn`dGG1|Q0|F1eo> z`m9dHmm8uEA6c=qN0#4q@g0a>gAczv_uDWqL%{wi?4IBFq^G9Q7kcwMx?`-5fpuof8V14S| zhMk)kLtOfb^vgq*=kHT;Kf1oim4O)o_D`<exv`h&I_`&VH`uVg=21I}l qH&d=YGU0)MJE>cKJY|F#5+GM?BQ55d$=nQd6@#a%pUXO@geCwZ`Zy^7 diff --git a/docs/games/Doggo/img/Doggo-level-Vector-3.png b/docs/games/Doggo/img/Doggo-level-Vector-3.png index db5b276b706080aef31885773456aaa877d681e7..1c46bf9046e323b878cabc8358f6200f57b1086f 100644 GIT binary patch delta 162 zcmeys_>*yhO5_1g7srr_TW{|g@--;%I9$9|kiWBXr{iO{3E#uj;%~9Uo>{?F``VF# zTTCZnLxN%hYj*i!`#R;>muJ>LKJl&gSW2WbQ2d|}Q1)AV?8YxjGn|6U>X=jJ1$zL{#Ljayi`#dHofvA%x7`){A@-4q~`ds@Q5rirz7 zW?g3<9WaUn>T6O3>VCWL!mo=b!3xB5Sb(BdD_EOnOWRFzkdzmvv4FO#rd~ BME?K) diff --git a/docs/games/Doggo/img/Doggo-level-Vector-4.png b/docs/games/Doggo/img/Doggo-level-Vector-4.png index 5512a2edc4aa6f43b7cc1f5943e1127319f8e2a2..24db3237c61ea6c33719becb44a299454737e16d 100644 GIT binary patch literal 428 zcmeAS@N?(olHy`uVBq!ia0vp^`+#^02NRH7+RM0xfq}8u)5S5Q;?~<+j(iOU91M;I zf4Bd&{L6T8u~G5c-kgk}vn#kN-WxG3X)c+-cZu<(f-I0VBjxMy@|5CTml^)++}7va z#;2zPmiJ%>$sg9Z`G0oI%YzjKGI5g&6BR)^9IU`{KqDMMtOk%_AQpyf@*_V z;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/Doggo/img/Doggo-tile-stick-Vector.png b/docs/games/Doggo/img/Doggo-tile-stick-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..f5ac61ae05b7a3b0238d7dcaaf447abd9020d32f 100644 GIT binary patch delta 39 tcmebCo1mk#CjH0x25AGs2dv7WQVcgL87G~qPkqDy1fH&bF6*2UngA(d4l)1$ delta 39 tcmebCo1mkV^5g$}M)QQE1Ke(_Oc;tdnS|``sWdSFfv2mV%Q~loCIAvc459!4 diff --git a/docs/games/Doggo/img/Doggo-tile-wall-Vector.png b/docs/games/Doggo/img/Doggo-tile-wall-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- delta 46 zcmebCouHyA;_2cTQgJJJ&HwWa>;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/Doggo/index.rst b/docs/games/Doggo/index.rst index 0f79d6a0f..db8e77df2 100644 --- a/docs/games/Doggo/index.rst +++ b/docs/games/Doggo/index.rst @@ -1,6 +1,12 @@ +.. _doc_doggo + Doggo ===== +.. code-block:: + + Single-Player/Mini-Grid/minigrid-doggo.yaml + Description ------------- @@ -84,6 +90,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-0.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-0.png index ea100a8987576ba1b87fdf5eaa7596b5e0f4e1e5..2b00f2da24e7e40fd97f67535815c1b6773df8c0 100644 GIT binary patch delta 173 zcmX@Xc#?5~O8o>+7srr_TW@bU@*Z*!VY%?%)3u><(F#2WR-*tGHLc`AgLXL&?)-I= zedEk#pGn<)EXMuxN``_212DG>M-F7|QaseQJO@xg Sxwwe|2s~Z=T-G@yGywooV^M|x delta 172 zcmX@fc!F_)N`1eli(^Q|t+%%havnC|VGXb>>F~SQyQ$UIb&sprE0Md0ZaT1=x0u<) z+PAd}a7a(zc=Y~NAx2&hsLiweRIxU4*Y__4zYlGdE?15R6(NC!#-|ilL7%NCBB3O{09$h7uv)C N1fH&bF6*2UngCYNP8k3I diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-1.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-1.png index 5253f5d25c1f99bed6ce940f23984f239bb47122..375644c1349d562a8b5a605167f32836140ed41d 100644 GIT binary patch delta 196 zcmcb?_<(VOO8s(A7srr_TW@baq}`=H4w;L`WM zye#tSKME7vs*lE09w~8j0)bt@^S&0Ie*bdy<~OR3^S(V&1cTU{a#lBeIKklMdk@LO zW-NP)HqNWh*v+m20!vmdKI;Vst0IUmDnE(I) delta 188 zcmaFBc!P0*O8tCK7srr_TW@b4TSlQ=aJ48!T;9KwwGM$$v2lieO-SiuGNw V;N)7RX%!4W;OXk;vd$@?2>?dyQStx) diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-2.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-2.png index 030509943623769196ccf8e4c699542b13e984e3..a214f7d1a950711dab094c16922ab60a7083ab22 100644 GIT binary patch delta 234 zcmZo?YGaz9Qh(Od#WAGf*4sN5d7B&rSS~~-3x*|1hd4@xB}#@lO5SQom67e?Y*YC3 zth`uJ|Mc-E9xJY`f9qknwV71{3@Ucd`TQgHyuscrT$j6K*In11H}}!a#E->$FCBn@ z{g<{BO^?#w>3!1x0zTNAtbKp$lEia!X|`lGFu1lwY}qg6ZwAs3E}LD&FnI9Sd~L4X$J!S`$=hK>$8;;pPOT zHA=EKTccgy9O_$hNbee-TY=%kCx2fn{F-4~6QJ`}wtlKRiva|DNdI)^_~R+Q(=RcYDUkl8&NZ`}$0v^0Bk7IZofD!ybF8tq}t17i4~2;mdKI;Vst0CaV2SpWb4 diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-3.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-level-Vector-3.png index dcf1fb12900ea8677dc938814187645db996d99a..25975ec0b22c0e0a92bb57f81963e97dcc28c5aa 100644 GIT binary patch delta 309 zcmX@dbb)DtO1-$Ji(^Q|t+#hCayB~%usWD8;aKf)Y6I8l4P8?bXKmmqF z?vdxSzj7KpyFc);EY_axS9h9|Q72+U!oj9i2C+4Ji_i7PfAsMCtrxc6;?ivA&h(>` zr*lm>nG)#?QX>Q;WOp3AUh?JbC%xEP|Jr-siK?BJI@H8k&pqux6YJ`)Z1>ZCb!8uo z@R?@X!U`7Sp2k)`=S=4B{o<2Tc@qydv3djTT6(bFyVKBiK1i7=$a0`slPeDvpUK>B z9(hdwWEQuWPQ<04=Ji?sPygz_d0GHs4@fiX>v-uux^Hh5-`l)?_m+amAf=d#Wzp$PyG$%;?_ delta 307 zcmcb>bdG6)O1-G3i(^Q|t+#g{ayA8sum$9sv;^;%82v(IS8MQ=w%{E{vm1A`^q**d z{`6iJcZEF@!xAmE3+QRA$lwsw*R_`nQ$29oz#r`jBwfkr3 zWOIOwWdahs|GZv){A5OBCS zcY#t&>+J$jy~CkPctRhV1-0&$xX;9B?4~IFKI(m}Yg2@svP6%FZa?p~-vull>@O8$ zmoQ&)v;wj=sC(C1KF&TcyRV4(->T}qJ^y~?8~a_JyLn|ZyBJu>lID^Le3uf##oRxH zAG^ab8>CA?7AOSL50P}V0vZTb#t1UHxx@jY2y6+@;n{j-J5tyGui*%0=huO1eAxgr z)p@h;y@X?P*RRB-msqO|CTiZFJ*QA_eLUNq=fyu+_g@YF@!%%;Qj{=h%-?p>e$T7&eF*a)4%wl8`$_cbZL8bA<=qbbodhur8XF3- m6&g`;bD4m8kV7|MH*-X^aM#5OD}x zS;65yv3HZJZR_S%^G_l-o&IWzH2L^UdFA^2&nz)zJ^c?HUV7P0!uw?`?sExZ4HjsMdx_s?mzTc-!J%SQl}D9D19c(gC{>3a}rb(do{NFz*? zeMvLK2nAUPixDUVWH~?-EnxghdcY8ps2V3f_p!nYR-2LgNE^m4xyYKVfU(9vI2e@bYqB=~%>qKtR z&bJTmRsP!{|G~HvY9hqiL=E5VC$qo4eDnpV3*>Z|GZnj*-A?)asC>t=TdGpgSzniC z1cSl=;jlI%%cd8%-gcwe_F#JMC;9(hZR3H;p)8MR diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-bookshelf-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-bookshelf-Vector.png index 8722650d1f29647aca02a0728f1df6721682cf3c..bc5c2b3ee088996e95dd51d51f5d9ef337bf7373 100644 GIT binary patch delta 39 tcmebCo1mkV@?-yGM)QQE1Ke(_Oc>zmvv4FO#m2q4GI7N diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-chair-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-chair-Vector.png index c59c09c03c6c4a719238e15ae4653fccfbc1853a..cddd4d04ef230e1c5b98578b3937de5fcf9273db 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJJ}=HGlq{)D6iiK)j}7{d7(f5k^sI5Gf%r>mdKI;Vst0Bcze A=>Px# delta 47 zcmebEo1mg4>gnPbQgJIe<-+gt4bldN4_K8$r5MWE7#+Q(baEJgz|+;wWt~$(699w1 B4+H=J diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-coffin_bed-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-coffin_bed-Vector.png index cddd4d04ef230e1c5b98578b3937de5fcf9273db..1ea637a7b36f451fd81fe5aae10ef8514b6d8c15 100644 GIT binary patch delta 38 scmebEouH%G^YeTIyMduWk5@7i!+J}`{9TiaKQRD-r>mdKI;Vst00^cH8vpLttjeKM3}tPMj^0u_ISfGH>FVdQ&MBb@0Q;f~wEzGB delta 38 scmebCo1m+Bzopr02gr$e*gdg diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-door-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-door-Vector.png index a3febd5ddf04e57857f0e3545c11b581ccf14e41..91397096b099e57106c2f0ccb43b268e2a43449b 100644 GIT binary patch delta 39 ucmebCo1mlg=lq4U4bldN4_K8$r5K*yWt=zL`}b7_AngnPbQgJJ}=G=$*jOGbR2e{o;{GgJzmL73@gnPbQgJK!&-n{y8>9^kAFwKiN-;dY%Q$bg_wTC=K;Y@>=d#Wzp$P!M CED}rr diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-table-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-table-Vector.png index 1843eaae9c2f4d335fe8655911263bb472e49a3c..bdb0ce86ae6361287f80a61213cd408e7d47d1f5 100644 GIT binary patch delta 39 tcmebCo1mkVa^vTGM)QQE1Ke(_Oc-P@GM;%+8W6|;1fH&bF6*2UngAv<4dVa+ delta 39 tcmebCo1mlAllbF&gS3I+16JiwDTZ}cjQOWKH5M@dfv2mV%Q~loCIA!V4GI7N diff --git a/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-wall-Vector.png b/docs/games/Drunk_Dwarf/img/Drunk_Dwarf-tile-wall-Vector.png index 449dbd771182fef12e06a8a184c42c601fc0d169..a3febd5ddf04e57857f0e3545c11b581ccf14e41 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VKt4bldN4_K8$r5MV?868vhEAlY_fv2mV%Q~loCIEop B4*viE delta 46 zcmebCouHyA;_2cTQgJK!&;7Fv>;{GgJzmL73@+M}fu0sX_4i_a) z9xVR6L+H$*8*En|?b-g;?bvw{FHhrR92W1>SUlKYD#$KjzT{{HWCeIe$IH!JdTiq9 z^w-N0Uq5JTEt$Y~i4mlt8OTCWs~`)M0_$o3DRQ)eDne4;-k){-Ph{=S%mefOSww>@ zf@zUpy!3FjQB8SYc;OE&`Me0Q$xunTS%Nm+=da}=+u2;=0CFF~r3$jj3Fl`|)~ieX zD^?Kq$vk{^+ggwl5$;%OVO}ZMo+&dAC?PEdb}85smlldVSNp$HF1fPw|IZ!hnksN; z!tfr3dn``wJUKZlJ@N*`p)j8waGa;+KWBE%YjE&j=z=O+a(G$K)7v`|AgbUYEm+9@ YV}@}>#?F#u!02W0boFyt=akR{0EJ`C(EtDd literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^`#_k32}rVtDV<_qU_9^X;uunK>+M}fu4V@whrr-H z+lxP!L@(@HbcyfT-nG(__JU3uH$Gue-y_3#$`K}exkqab@j$h)s1`fohr_BCH8 z9JApKMs<(Z&6{fBzqd8($I93P<@9xs9PO;RZ}y*piF>CO9Q(<-6XF6#E2zJg-cYl@ zGqrXjnlQrgDDHp;9mu;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/Eyeball/img/Eyeball-tile-wall-Vector.png b/docs/games/Eyeball/img/Eyeball-tile-wall-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- delta 46 zcmebCouHyA;_2cTQgJJJ&HwWa>;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/Eyeball/index.rst b/docs/games/Eyeball/index.rst index 79a08abce..82ab85e12 100644 --- a/docs/games/Eyeball/index.rst +++ b/docs/games/Eyeball/index.rst @@ -1,6 +1,12 @@ +.. _doc_eyeball + Eyeball ======= +.. code-block:: + + Single-Player/Mini-Grid/minigrid-eyeball.yaml + Description ------------- @@ -48,6 +54,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-level-Vector-0.png b/docs/games/GriddlyRTS/img/GriddlyRTS-level-Vector-0.png index 92dffddaf8577bafab91b14df23ca3aa09c5e138..674000fdbb13f65d679dde7506112cd221a7c38f 100644 GIT binary patch literal 891 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov)PZ!6Kid%2*9_(#45O4^T z*~z-gGdjdEx@5A_p6#K2(>5gce5?*vX_BesZ4VThrmX8=ukX&F#3IOqjIVe7JpEEu zEAQXQxY)UNlR0kgHJL21r>}{FQ4oO}yN^yY-!9Corr04MfP(M%$$Z}Uw~ALyQNc+7 z1qZBne9Y(UweGnRK28oT3MhC%!0}$mX#E5sh!u`44IB{czK7$`{_ zi>$tRWn*qKG$cWh1`SDbv$VDe_kC|#->v+76ck>lkpR-mzk#20l}ZqQlI*$&F<#v)t($30xl@{$_7K{jb+uk zuS1?^0KJ4r7@(*+a5U#kKu#$OW-9DCag68B>(BWCYKj%Itf&cGocr{|IZRjrpx-|8 Xm7+wxvvxNyQ!#kD`njxgN@xNA-!%<| literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_ov~PZ!6Kid%2*p6zNj5NQZB z{t-9H{PS6-`8s!b&m=L;y7g!7X3;fEYGTBvo8`FQYbdLeVeAla;XuYU*W>ohiC_5W z*~$u2t6DL^uIfxR#R^$gK_(>@1diWkUs-Hzm>|@|!6=A~oyxxn%Ky^&H}Q&O&Dt3) z4IGXr_`v>Ixo58A?7h)>P=&>XLkR`vWp6i3e$DFRB%r7O#2o@KoRL`0B0qUo&8ipO zhMcJOHE}fB=bSmSZqM=L`NtqeB3!A|^7Y1yor|7@r#wn7hsq*bx^9EvXUnx^NpBuu zaj9scbb%7rxN2 z0)-a@OHAsOe7EZ6m5sT{NRi{%(#HVu`K^tH#!a8{ymCsOKLLg%YUDIX?=4u=*6CuF=} zjb*Lny+e)s_a}#oFF)_v@+5ASCr6&UV@m@E0y`vro++QF&gSDJps0X?GtS+4e*W7R zDIXM5K{hA^AMKr~9IaoLWWdQN*u;U18+}C0({lDUcycJQxB#&r6AX)<>3dN8&iCJ{ ztMbQGz;YmY82-1((0I=mx&B(I$pS7f&^%LMX_j`c=!vG$>&*E*o*W$lE+}}hEO4g}&BcdolH1NL+3ug!dH>|yP3@pC zLiQ1(pp$c4&Y3Mc=N8DFzj9Co$xC26S|0AbG2>-n^OB3Y^YZFMDU8MU_K~!J?ajB?qo`V|otVvmpMHk3BOP?`YS)fAzu_CC)%` z_#$w#v~l&Pq)(=<_Yy-+O!S?5Wt;Dfr1Eq@xu5%Qs<3QdjFwIe&d+%1RArvJ_RGn1 z+uLgQ4kgs@|H3x`TUs$oV=H+7VX`47A^|}RTOcKE9Bc7j(G@w|)K=weIUJT^zDU z1u=vEg0i&nhmhyF0cwgnWLXjE6%@Ixt(o0^jF@?2N1uG0xw+`O6LX7!d6&V{)z4*} HQ$iB}-(W^6 literal 999 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_tJJo-U3d6}R5rblX4DMN2_#s>~A~DamnB*@<Z+9|I7I5L{K*6(&zg1lH{-L;1^=G6Z zC$h;Pc4K|$?HQJvz9y-&O%@PP>_EXc*4{hTvsT=I)3K$2!x7201#K5~f6Ul=a`mjg z25O25P68-I#82UMrR;^@s2S>wOa+JEofTx$*uW>8Q=@Ir5hk4yKlPx&MwQ@iB- zgBNf2C2lFJIQwv$FV}x{Uz9We3akeT?`OPp+#_rpZdJU|cWzX5)YBu@4r+=OGOU73 zN-PMhzS;2d{;h|NlZ6n;&9Nnq5#+Ed$;r#upWait{LFMV)G070Ikq@>nx(Bf_pA%k ze>T}A$!g1WZRY(cnt`DW;VXnu2eS8_aw(oBBe(YscN-{5P?Mj<%wEYdt?k!0-FdS( zS`8?P8iaGs9OJRRo4fv~3X-?LUTitN!H{|XwGS_Hgqn^ABBvpTmIB_)8Jl)9fQ&=6 c@6K`lx3e5w+Oy900y8j!r>mdKI;Vst0CGuKiU0rr diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-level-Vector-2.png b/docs/games/GriddlyRTS/img/GriddlyRTS-level-Vector-2.png index 766c9ae332da7052fe6db6c46cf25ceecc8fc2a6..abd8913001b99ae6709c51cb6da84b5e71779e07 100644 GIT binary patch literal 2212 zcmeAS@N?(olHy`uVBq!ia0y~yV6p;Y7Y-(%$OI-yH3kNbY)==*kcwMx?;I?Atia$9 zIB_BC)dy-QYmY}XHFXoz71 zB86}U2s$Ck06~j8pFZ5u;wQe%?AM(Qcm01>OWui-nd>X?er@>5%T-&;e(f#at<7*u zEgY)8h?N11Am%yf&HH*t?&!s88ks+pDfUS1tJ&2aE$fzjdInLDdHIb3@FGuJ`KK_x$8x_%FC5ssGJ71_^k& zfF}ync*9IIK>4+n!u0@Ij?J1nUfVrE6`jBP1md=sZ#crGUPK4JZ8smoQTRu?@xdgHv) zhGVy4vE+Yn3>{eB^)xYEiQ&qQM_RXD{h7Yu?pt8Gm<=m0(9*@u46XFimMC)CAK*@47FSybwF`WG~-wQQ+MBSHEcX7 zid@dXlPHd&g=l#+ufQA**N;OUYZd?p9afJ6O+k``8xD6oA$g$Y=c2v`kG@^DVE+dn zHAY~-p(PqvG4kND#nq&WOis0jZ@y;~^@Gv|YRUm-DTr3Q#nTG0WxAz5A8l#*uD|Q1 z>Gc@V0|&WFc5i(bwvoyHcgSI2gfQZ8^MvlF4~yr0DmW)>_#>mbu*A%9PPXcrU-wpC z{XXqFzS;mDDma3ikUagP1b39ms6sM&pv+xuy_~Xq24HqauA<<1{Rvk?;n8XGadtD4 z%zp!GHb2=-NCg8tt|01Xt+u(k=-J#?H{URu6mK=who=IJs7$cf__Te!=>OjZ`3zQT z&!@*Ug7Pn}5Zq~yds2rXp&V3`1Jytfa&XGLmTr^C6ZYP;rxe)wVDNPHb6Mw<&;$Ub Cq|877 delta 924 zcmZ1?xL9z4ifNXoi(^Q|t+#g^&plFLXb7B9pnqxd->)gY{>O6CES1+D*GfQk?YBwaH*thcf zxjhUo(sZw0wBSsu-L{)4fvJdfVxp*%%u?yPWxwiIZPu4vkk-5Pu==%4A@SMetOvv= zRJy8E#=V;K!>cTT>@E+*XP=eM=s6f=W7SI}1XvTz0;hjpOPnOwleKluS8 zpF_jFNt!ca&m2|Z`M!B}^GCUzW>McVy54|&KS4W~p&^D5h!nyZAn1fBL(QaM-Mn-5 zKYz;gKWGu3{GREVK%U~{d<1C^-UXY zh%CfNxT!!Rf#^^BPU2Zp2M{g+Qd zxYbP=w(NMQb?fb)>Ax(!5S|igSe(N3VE$68UA$HCAC}wzh5|P@6vX=GWHFx4{Heab zSo^~go6le=Yp|3cNb3EAJ6sv{L7RZ#JbChDGv@iwfDYncr#-W&I@;Ng^RVqEONQN( z7cxqU{jf58a%&}fYX38i27i{7x0H8lGpH#{Tqrgf-fA>Z(c(UacOa`F3L=6%0Y z6wZ(zw_x|$Qv2Vp*Vm@(o}9=iDgQuIeR=xjy;*zmvv4FO#ldFj%ok^ diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-tile-base-Vector.png b/docs/games/GriddlyRTS/img/GriddlyRTS-tile-base-Vector.png index 0dea1ddcafcc14f793d3b3594bd9a39302c7f696..40f1980b9b0683d6813528f049be942d1565f878 100644 GIT binary patch delta 37 rcmebCo1mxgVHTr#Lec?lw^b$#(L9X*LbV<)WdH(CS3j3^P6;s5ytX#>LttjeKM3|+q%gMMoJ2`~VGr>mdKI;Vst01Rymw*UYD diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-tile-harvester-Vector.png b/docs/games/GriddlyRTS/img/GriddlyRTS-tile-harvester-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..40f1980b9b0683d6813528f049be942d1565f878 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-o9t7|j!s4sg4zGGU13Vf+`W^>8Tz5O})!xvX;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-tile-minerals-Vector.png b/docs/games/GriddlyRTS/img/GriddlyRTS-tile-minerals-Vector.png index 6985a08783e70b5bd12babc65a4dabf7d339a02c..5f7dd9cd6db566faa415255c6cbd8c5b5ca0832d 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-s8;4bldN4_K8$r5MguGj{BfJoJbG2s~Z=T-G@yGywpi Cm=O2? delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0t<{FhqZ6{MQ$Lw2A=;JYD@<);T3K0RWdK B5eWbQ delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/GriddlyRTS/img/GriddlyRTS-tile-puncher-Vector.png b/docs/games/GriddlyRTS/img/GriddlyRTS-tile-puncher-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..40f1980b9b0683d6813528f049be942d1565f878 100644 GIT binary patch delta 39 ucmebCo1mlg=lq9RjOGbR2e{oz`$7R>EaktaqI2fgX@|M1RMgh zez^T{`+0I#scA-LF8A}CvzMn=87t0I$UMn?=g46}h4Km3CuE=S6|wJZzT^1r!|tt5 zXYX&iUQ~7O)q>z1d(U}x-Jdu0B}iHsq4Mzjxp!9Gnq7DL-j=ZN(?R#9zU=FkL@_5o zLG~|G!S1~;<@W7z{r{!qd+jot`)pNbiu?SH?(LlQZpREpuzfJ=S+CW}?=`J{zW4NV zx!m8U{%?DFZtLC1w{!Q^UY32*2Xq)rVNv@jtGrP6JG%nUcidlqY43wc-&cLR`TS`4 zl)hiw2f}wJ-zY(Gdh?xyyAD5Jb?jMnn1--R<)f1;mjV5c;+CsG&mDUcW6x)0^L$R; zwAjsiR{{gT9pMD_ozA7c+gBy*0)-gVVIVge>@{~VG%0^;wRr|GJ{UY*{an^LB{Ts5 DAynFv literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^zkoP}g9%6~JlYk>z`!`k)5S5Q;?~={2YU}02si{* zySi`C4HVH8%sLhK-K5cn&F9lSQOA~_o0NKee%0m`2-r_!*x7u?@ts2X1nU#BPxy-3 zug`sD_iKA>bNKT*>nl$F55N4nwDiZDsGD~|ihv5(q3SC(&$C>emFML-rTEI#y=&i} zde=8w5^4xU&4xpVLbm4R-CBRK^?7tj#b@dBwZ|qOGE~2iyskLySyuV+vpircVJ1dA z{IZh!c=6>P_wKgF=V7IHD{f7%%)fiaG;Z~@h}CDV+<6DFeImrA>^lz!*qMdO7QZ?c zcI*%9+jl!=GNSlJ`^|?nS$8w9wC0P<(~$iv-|%?rETQ>t)@UIs1p4Q2o{mkpY1sEG zp?tEbk4~;E1BM0Mn;;*VO`o;u*qNCBvk#T-lX=76|N7Z7Of}&ap{%=ME&~NA$Zysk Z*k3I64a;s1n*xj^22WQ%mvv4FO#rEl+}Z#D diff --git a/docs/games/Heal_Or_Die/img/Heal_Or_Die-level-Vector-1.png b/docs/games/Heal_Or_Die/img/Heal_Or_Die-level-Vector-1.png index c04064c983944e60c329e1d86c8ae0d1e7676a84..e08cc64c5b478f5fe67d40cc6facd478b10314f7 100644 GIT binary patch literal 715 zcmeAS@N?(olHy`uVBq!ia0vp^zkqlj2NRH-?A|KCz`!)g)5S5Q;?~={-hPJ-L>w+k zr1qxvmP}4N^+NT>6W$mTQ6wNiF>Ycba!@YIY`k&umZ69hljP6uX?tueCF(JuU=W4+P!J_V*_Rs zlem_wHfQL!FrWKz=lLnmXFK%oJonM-m~r@SYd&L`U0@9h5A1v-|CP_aw)EH9@XPx? zz5h06r`_hTGN^liYLvlDwM|b#zCQh*wf4h^Ygyo^~dF9Pd>x8-N>E+ zYCc@}IPU5UPes{9Yj#*(eaBbi4)-ch*133U@YM*LV&8}5dykfvUF=<}dHm+rS;ueR zEu6uK5-zN}{6dv2BmeYnu3lk<=?RDH)uE5S@2CnZ&3*qN`24D-!#tqSgu8y(!?@66 z-Iw;RdH@b5h?ju!?Gt1CLfz-gKK8y}_Ve9CAD0xI;rue=b&PTGR?HxsGi|}t)tT`> z%Wc1AgJZ<;9n_NMI}aVb{X!p0+upsg>wVNsOy^IVv&znxyJx%N!vs16<=4An$0QUzdeJtF$ kE;w`pas+^kFxVU4Iyv^TpR!~-Fp)BNy85}Sb4q9e0Ic^~pa1{> literal 746 zcmeAS@N?(olHy`uVBq!ia0vp^zkqlj2NRH-?A|KCz`(T8)5S5Q;?~={fqAP91RO3d zOmg?!K4p#O&7c#rUgYm@6M39;#&Un){4Z;?PhUDwQE`@0{bMTU1nU#BPxy-1cQ)T~ ze5X+UWB0~;_J3u!Ke=96F)wT3@{+1r*(E<-M%}y%l0{aS?tC&fv^aQ`i)DK3-lW>_ zvT~U;2cR0bfrq)cgRh!BU*^8&`%HhX+jAVY?>wh!9KPF{&lpKn^PR-jjr;#!vfh;V zKsAe(`EzFc$@|x4?|hamdhvF9=i7G{XBxmRgPPkeYBgt7+#%(qx7S=vDTg^5CaJf4 zRrSgIDRLjV50oKX0`XeM(elvZ+ds_&?`O+EhQY%KWSLob;i_4?rDo5&u_d?^i`PZ(hsN$$T~!>uHJ!UW z^l)rw!3;)d5Q78paAHOH)NhNvtkZ({4;~2%RUx7F^Lz5`KidlIG;@lX?mn}cekKM> zP`G(-m|Aly{L}Wlzqh~kpyvYy*jeAt(o1vi zxt+b9wP`mbtPqj-X!oj}X7RC4KLm>HnUe(!aAo9hO)Pc|zB*^->-^wd=dMq#ownoN rohz6DtK}DZ5Gaa>EFjaFy^bN(sd7f-DSuU98fNfx^>bP0l+XkK1zu_6 diff --git a/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-healer-Vector.png b/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-healer-Vector.png index dabd04345356dd4a014f1bc28dc1420c1c7a6afc..a9977b2ea0be21af64a2d53fbb4833815831e79b 100644 GIT binary patch delta 39 tcmebCo1mlg=lq5)M)QQE1Ke(_Oc;128TYDWth~@@gS3I+16JiwDTbZT7%TmB_H;4;fv2mV%Q~loCIAzb4K4rx diff --git a/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-hole-Vector.png b/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-hole-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..b300bb8b33579fe0e5f244a8ad7f2e8946c73064 100644 GIT binary patch delta 37 rcmebCo1mw#e-fj4Lec?lw^b$#T+bQz{F35mWB>wBS3j3^P6YfYo delta 37 rcmebCo1mvK|5$^xf#CyI>FVdQ&MBb@0PBhih5!Hn delta 38 scmebEouH#=^LIWYe?n4%#MEOf45rSE_x@M0?P34|Pgg&ebxsLQ0Qap7djJ3c diff --git a/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-warrior-Vector.png b/docs/games/Heal_Or_Die/img/Heal_Or_Die-tile-warrior-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..a9977b2ea0be21af64a2d53fbb4833815831e79b 100644 GIT binary patch delta 39 tcmebCo1mlg=lq5)M)QQE1Ke(_Oc;128TYDWth~1o)wG0eQb3I)gLn>~)y?ZdX*+8V> z;f^G6BbT%nWnhBrE>E}#rv1NJ~kcBPdZv-xO)Ee z^Ljo099x;1bxlBF0SOOK=r*d%^GTYyOzMHC*_o)V88a_yeKmeKdCgPnhp{t{FhPul z#{wg>vq7inA@5t6*H*Q@&-|Js#GSJc9zPI|^BjEHVi|Y1VRM-7nas86*iCayF z>S+d0iT~j_(?rptPaz?}Lb^`)bw-_E+S-3-)GvpGp*R_2VH@*KX1~gSui5Fd#DVFR N!PC{xWt~$(69D_F13&-( literal 718 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoS8*@_$>1o)wG0eQQ$1ZALn>~)z3Z6QY#`te z7*psv$@rMerO43Os|zF!&)BoQcb1x3e462*l}|V&uJo%iFfuc-vG9N}hlGFuheSu> zy^6nyolGazOOo%?t46FDoCee-N^}kdXXWk1t9P}+g9Is-nhLBQGF&sdw|v$4%&&7QH!-7{ zbwjA3p^+Qto2_$VUsOizK00UXtM9#Ysxz=SPjb2NyeD;=!~TXHo-<7r>KdeQwOEjyk*$8(RjC_|D2WB+^nE9!7fgnPbQgJIe#p2U^M)QQE1Ke(_Oc=Cz8LuxWbUn`i1fH&bF6*2UngD)Q B52FA8 delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9 delta 37 rcmebCo1mw#e-fj4Lec?lw^b$#T+bQz{F35mWB>wBS3j3^P6YfYo diff --git a/docs/games/Kill_The_King/img/Kill_The_King-tile-king-Vector.png b/docs/games/Kill_The_King/img/Kill_The_King-tile-king-Vector.png index a9977b2ea0be21af64a2d53fbb4833815831e79b..5f7dd9cd6db566faa415255c6cbd8c5b5ca0832d 100644 GIT binary patch delta 37 rcmebCo1muJt!0-X9a;Ox;*=oj)U6O|$F#v(5tDnm{r-UW|=^_k5 delta 37 rcmebCo1mw#p^MQxA?X0O+bRFVdQ&MBb@0QbZUJOBUy delta 38 tcmebEouH%mXMYzXe?n4%#MEOf47`UK_a2+I@Hzt!c)I$ztaD0e0ss=D4ZZ*X diff --git a/docs/games/Kill_The_King/img/Kill_The_King-tile-warrior-Vector.png b/docs/games/Kill_The_King/img/Kill_The_King-tile-warrior-Vector.png index 5423baded82d810b8792b647a8af95a95818daab..5f7dd9cd6db566faa415255c6cbd8c5b5ca0832d 100644 GIT binary patch delta 39 tcmebCo1mlg=X}7a25AGs2dv7WQVeIS89R1K9(u$81fH&bF6*2UngA$F4h;YR delta 39 tcmebCo1mkVlJMhvgS3I+16JiwDF%BzM&ZzXm)#hEz|+;wWt~$(69E4r3>W|a diff --git a/docs/games/Kill_The_King/img/Kill_The_King-tile-water-Vector.png b/docs/games/Kill_The_King/img/Kill_The_King-tile-water-Vector.png index fe01dce2a38c653766e302c6e6bb69e46b105be6..0efac0d902f1e12d2a33b5b665b319814988f7c7 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe6*p00i_>zopr0B#)* AasU7T diff --git a/docs/games/Kill_The_King/index.rst b/docs/games/Kill_The_King/index.rst index 5ddf0bebb..87f9e1a8e 100644 --- a/docs/games/Kill_The_King/index.rst +++ b/docs/games/Kill_The_King/index.rst @@ -1,6 +1,12 @@ +.. _doc_kill_the_king + Kill The King ============= +.. code-block:: + + RTS/Stratega/kill-the-king.yaml + Description ------------- @@ -55,6 +61,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render(observer=p) # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects @@ -240,6 +249,9 @@ YAML BackgroundTile: stratega/plain.png IsoTileHeight: 35 IsoTileDepth: 0 + Vector: + IncludePlayerId: true + IncludeVariables: true Player: Count: 2 Termination: diff --git a/docs/games/Labyrinth/img/Labyrinth-level-Vector-0.png b/docs/games/Labyrinth/img/Labyrinth-level-Vector-0.png index 3bcdbce0f04f5ed34d081d6eba06b43378ea1e58..a2099d40e9152a0ef2959039fcfdd9182387a6d1 100644 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|`(t>EaktaqI2fgT03p1RMe* zb}q^B);pzZxw5mwU|Q3fgCFjh-b#6^Z_(2cd_HW(jrq|E229EjwBf*+q}M+mhs%DP zbZ`6FXPef24_@S*wKnSDwhR`x1SpC-Sav`9ym{cMUH5`kiS`_TX*OWmoLXC8)B9>B zSL)fFRd(k4?)~4yzJA`*4NbWhIeHF2(VnKao&QU&Wv(y3WPCKrw(j5hH3`usFhe#R z@L7D!zefIZbvw@zgW9w$xudgT4bwPk9jxwfHLq*mj1t!97D3m-8i zZn)!pUd~B5y6KtS=a)Of!#3zjH3~|AQAEPBLc^1*s!d!hPrq+(efN7??4NU6-yU4O wR*pF?@Qm<%lo05svXV|^h5HIEaktaqI2fi@AptL>vNF z>iVz^x>5*L>hn>igFJwB2`OP37e^)v1$GyWtkNJ@Dn3d-7Ip+Lt5MsjRmbAvwI)wSM=9&hsau zKF38K+?D}#ky}Ee=i{VFrms~lc9{BiotR8^bDRNFFq?Yh#>nFNHE!>wism33CN{$)i+j1sUiZr@q849Y_fvQOFTVBP w&wN|mfgE%Z372-vNJ(Uc`xO`_I*+&yO?2+q{_JohFcKL&UHx3vIVCg!09oqC<^TWy diff --git a/docs/games/Labyrinth/img/Labyrinth-level-Vector-1.png b/docs/games/Labyrinth/img/Labyrinth-level-Vector-1.png index 24d5ab3c2bf2c530216d22eea623478b4f4d8d4c..5b6459ca559882a3ac44c7b0d111c4470d19fd76 100644 GIT binary patch literal 484 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MB>EaktaqI0J$GpP|0uF(} zKiz!SPnoxJ@1$DwU1zfbx|bdMaL=sk(GPAh<=I77dOP>-WMxt|fFkZQI%V4vSo%MG ze?RXk*EUzUgz^U=smW`C78#ygTo+e#@%T5{*igxjZpS|_`*C?*eG2RCMS>EI5X71$ z)$4ifw-|3=*3SF#c9-uh>v`@Qe)jsBglH3(O&bpQEH*r8C2sAiJmgI3B*SDfc4OX+Q_=X*#CD4KxQDL_iH`0`^*pX3CA0Cli6O$KdJe=d#Wz Gp$PzAIK4gq literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MC>EaktaqI0}$GpP|0uF(} zKiy8woMo(klTSTn^e^oE&E_*LZ-V2a1@CyYDJX98A^z z|L>;q9M`CW+cH?(5+JB?rd#r)QrrBCPjCNKHjkCRcy->&fO!@7MB?B6>|T=)ZNj8% z07e@Q_$fzMdm#&pe`|(_|NVm<(nR9-Y~D*KBHW3w%<`}mwYk(iFRbF(qe@;P_%kXSFs!w?6r{AQS+z3>0b3|5f)H$X$8 zzuHd*sqN$WjuG;TLA4 hB!bO`8PD^Gb6&YcyH&@XxxkoX@O1TaS?83{1OVcxzpDTM diff --git a/docs/games/Labyrinth/img/Labyrinth-level-Vector-2.png b/docs/games/Labyrinth/img/Labyrinth-level-Vector-2.png index c6a15f59145d60b5dbc726edf4334d1103cf3f6b..d64e46d20e36711c295a6be2a39a16f3e7bfa03e 100644 GIT binary patch literal 484 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MB>EaktaqI2fi@Apt1R4@o z7zOPPYJ26i#rPucxh}si9RK!RU*R=3{zJi`GWpGg8~E)T4{-EA5mRy3@yh96ZZ7t( zk2cvgd$azpdp&mZ4=!I^8+~wF28&w)6zyy7_Pn<1rO(ugFwyw;{*V4dJxDT& z$Kd#m87ZmDjN^5yc3t+lW4Qg?yvrKe5{;~IqnGK{`dyp1d6n(j*$X0(-PLI6cC&cX zh9;mLaMf{_+%0;$&Zm5pyk~!7?#W;L^M2%^yHcw2H~Z>y#i4tRH@>P~n-Fcnq-+32 z8xF`Uesj0={SQ7zpbH>Q2bsL=!XeLVmG2q1fUE+c2vEpyEmezrZxQ(Fe8vH@sO>0H o-%36nDPIE%J~5z<8NPoQGdQhYXtVTY0Ar8A)78&qol`;+0L0nG*#H0l literal 473 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?L}>EaktaqI2fgSjmR0uF)x zTVz#DF9xqTxw5k)L8N!i$64Rkt-i{(ukCP$!6>rNu=IgS} zKUT_P_x7Xh%W~sK61md9Gn--;2}(3V(GS6-8vkp1-ug_Pf8^u2-|INt5?I7wvOA~l zQrF%*H?sJ>@%cS-AN+`!nJ_(r1*igy8gCwZoyi`vHE`9h*}faRO_-Doz-YsPoX2UC zr`|kQ(<|q3bzSk%%Nw7Wonlem0CQWb0d;1SZ%8?wwQrB_CHq!Rv|K`x|#i4tRH@+%IbIam4 zcU#~8;Byp)g$>A&!5NR#viE;ym<9C<*v~VZR{CDE&YSY9V6RwW6EuJ#Kq|yaKOZRv jxe1K8fhL_%s$*Pp+49YitT~#%C}Z$+^>bP0l+XkKnS{iR diff --git a/docs/games/Labyrinth/img/Labyrinth-level-Vector-3.png b/docs/games/Labyrinth/img/Labyrinth-level-Vector-3.png index bc65ebce3dd15ee57d0658a8d8106e08554fdcb3..c8d1e3ffeed5fb3c9e71a7fbf148ef53efab2ca0 100644 GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i)>EaktaqI1!gT2iL0uF)J ziV&k^gn01lV_q%vcdb<#dqI)GPUbz*fZ_O0XetKFq+xmOwzw3GG8BMYmk<998 zo4sb%Tc16w+1tNoe&4(ITb0@k!CshcItGVl#5CpaJace)c9ajyCT^fpeDuvC?Y;WS zLc?qHXCOO+`;0-ES!8A3G;`JOl_~44Pp*2q@B7q6)d#nI>C>K>;BEqQt^w11$GF44 zd)EEfw<@l_c=!JkEXo^T7WW)ja@j7n;^^}qP+K7~KEa{0*Tnh%Ol%U2fcehtf%K|d zDRV!c)BJ5JtOIi+&?ugjuWTb_L{_@IZ;rp5gzRmO8g}d1Iw<}*<5|O?x?L|=Drkit QFrpYdUHx3vIVCg!00~aDLjV8( literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i!>EaktaqI2fgT03g1RMgT zmnyH|%u+J7G;P(6QS&%vQFw3nqSzZXz5I(OoK;=hSr{9`)F>zcMXYHe!GAuPre4o@ zKCM7Jvq|nS*XC=6sFew{= z(S`#&iyx}3PO}!yKX~hH?XA7}>-Wy%EOUWM8!&O7F;JWSAa|uRiDlG-?CX2^?pZ#Tf+ zwbvczSHJo?^Y&j?HHk)6h{-@w?d`YQt?qy1=5rz}lW5Fbl{NY5>;E4Un*<|Z&TxAm zyy{lU+t24Tf13*HKs*F?3(v||mXUungMQfj06H4sTcEv%j;V1A!vhTH&CSC40%C^# UH`fL`0;7n*)78&qol`;+0OvBQbpQYW diff --git a/docs/games/Labyrinth/img/Labyrinth-level-Vector-4.png b/docs/games/Labyrinth/img/Labyrinth-level-Vector-4.png index 9069320cd0f656668c37de33cda8dcfae34c047d..7c90bebb2c9f8c3961529c22dc00cc76942acea0 100644 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i%>EaktaqI2fgT99y1RMhS zZ%uOYUbt+Dscv%g^i@iJNNQ_66RU9jsu$qzpkD4uo7h)~^3h zWUteg*DpRC&n+m}-^y9m>6X9(MGuTSKH5e;_Pw_J_s)Mh#cIa$O%Li_L*?hP3>%QK3pI#T5AB5`B0olB|!vC6n?e5ty zYs7%sW+Z#r#=fwPRD&210hW7~dTY=6dFA%u&;GvnTz8!X;%;uR^*&d_{uZQ3PrVs> z_IGT4jXPKa$RvrznX6UpBcS0hc}vb@(Z5y} zyQcq^Z(40uu69FJ3hEERh=fxW8&3YZb*%rk?f!W$19QWtRvH!Fn|GRJw+l2B4Vbun nBsZ3;AA~v&C=7JrALjdNG6r3@r>buNMi+yptDnm{r-UW|shzm> literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i%>EaktaqI0}N58`c0uC2> zKJ}iOS)^X1ddYaEZcfaSiLWz0-80K-{*|il=GRgDTDr9M2PcbL0u+hO(7N^GQ!@Ad zi_%BzW~tZJ_iDM6Xx|X)J;2ceMdbpIjUyj>Ub|emuKx6&Ni)w!9o&`yH=$8;ajM;$ zBiHh;&v_k*D)IQJ%Im!4>+HT?yjs^H3NwyL*??(t*Y}ja7x}d%8U?|K73iL4i-P>c ztNf=y73vs(6n-|#y%&Do`ux;N`(Jy0M;(MX3M8X!5WF?i{G88Xqs^--zkfYe4sj9K zz!@uVnf_lB8oaRTyf>O-lWzju^nTLzCXmGu&`_8(&399h{Mrn z>Yf=XuJ_r)&mSpRCV%x@O6okHIp)tdHRWD}2N6@S+nlHOcv?jvt_C@8!+|MZnD2QS Vd_K6^mlGIW44$rjF6*2UngG*D!88B> diff --git a/docs/games/Labyrinth/img/Labyrinth-tile-avatar-Vector.png b/docs/games/Labyrinth/img/Labyrinth-tile-avatar-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Labyrinth/img/Labyrinth-tile-exit-Vector.png b/docs/games/Labyrinth/img/Labyrinth-tile-exit-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Labyrinth/img/Labyrinth-tile-trap-Vector.png b/docs/games/Labyrinth/img/Labyrinth-tile-trap-Vector.png index dabd04345356dd4a014f1bc28dc1420c1c7a6afc..f76f390fd0b4e8d0d022acd2d642d131e07e7c90 100644 GIT binary patch delta 38 scmebCo1m-sWB#!QX#>LttjeKM47=(XE4QhuNiYC`r>mdKI;Vst0RNE;3jhEB delta 38 scmebCo1m-c@%wy(w1MFRR^?DBhMmtCEB$o#bTR;er>mdKI;Vst00h(w$^ZZW diff --git a/docs/games/Labyrinth/img/Labyrinth-tile-wall-Vector.png b/docs/games/Labyrinth/img/Labyrinth-tile-wall-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 zcmebEo1mg4>gnPbQgJKU=J1F4jOGbR2e{oSyKChA_w#RhHK9g|fq{#G!PC{xWt~$(69Bo$Lj?c; delta 151 zcmZ3>xS4T+N_~~5i(^Q|t+zK1avo6NVGRi0HN9+e-m{FdW|>!x(f9VZJ!p2|kXZa> zQ+(g1)D1we@0iu+qASxvj>|@VzRD}e&CQ*ioAhqlezmIIib;uyiMsoqezuicysxgJ z1=qS#W&$^R& BM4bQt diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-1.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-1.png index a515c72d1aa1f36e275610c5e5188cce8040f6cb..2cab70c88c2054632921c1caa892ccd98f5a91de 100644 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|?kOba4!+xb^mKG}j>m0f&oy zJJ!#dTbjO6yW3h~iQu~KpBLkcB%E@s)euV%4 literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLn`Qhz;N978H@y}f&ox5+`kA@JA@ z@e7i3bwkX`4A{+Qv)2Cia`=$yspi-jb8FGfy)~>7EsV_u2?A_9hd#92^S5~!u{|j} zZuwOO9=OD>)tXiM&!5awHec;1VZg!6mUsXtYm(7*uf4WJdTaJ|&GS3sRv+~BgiG!> zbPac1cl26M_LV(WnSSqfqe}Pdzx(w*>FS#5l&#MNzfQEhsEcITrrZDYc0FJCHq8`f z6VQQfBK|d(L!X4@oV8dBu^#N|37WUEKrU~9xa;g2=2I^(LbJqX> diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-2.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-2.png index 6def6d4efde28bce3d13741055248ab2ef0eabe8..2c6bfd83bc41faf337d1edb03261eaa708f7e0af 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfclLx;TbZ+?`c;SN`nNyt1$O)W)(8 zS*ACpK+FKDP3X#-?~!f%TzyNtVV|n+&vRkTzMeqs%xsB3yOzD^o4L7aYvmo6Uwpq8 z{H(e5eydcD7sLXfiX8_rY)i ziloh=o_W5W*}7)y)t~45H`^_ii|lIar(0j_2$}zH@6|Y+!b?4GqqZQq=lc9bsip}6 g5I+L_P*=;?d(p6`bI-qjz%XU-boFyt=akR{0LMz5;s5{u literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7#x;TbZ+Z^0oVx6v=2vyc+UwN@uTI%SDw>w|Hsk_6(bEv^l!hnOBE%8TNU4PvM zeXEZrj_oTvH?i8bmu>PSg9HIKuoAufUGw54-TIPVCs+H=H#It~?Ed(~_N44%8sG1s zC|t8h{q%}!C+Dgkn_F7+++LbjO*Qd=0*^!sW3xeo>-li|+;gS*?(TW%b1LqBl|q({ zsTaLldARb(pYXFv@1O2}RimF9U$WP&+0zr^K%gn&8h!gN?%nPFf6DsqnNvVUg5CGw ztL~p&H%=}6udz+e)-Y~*`b88=*GHO{oeJN1Y~S70wf7TEK%RxV=$Tc`;%GMnKf1xO bzLvo@!0^JZ*)DH^0n6a&>gTe~DWM4f=R28S diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-3.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-level-Vector-3.png index 44b5855686921226a89e20c12e5efc4a66a8b062..00d4ba58ebc1ff965944021b7250c6c4df56961b 100644 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK-!2~2zp2q|;FfclLx;TbZ+^yJfOfM(Zbkl@a2N-eoHUk zytUCLm6uXa&AS>ipWXJI^|ddBoA-}Qd zdep>X3djJktidm_zvt)G+^Iga%u&LCgBfCe$uZqsSC=pLg(?EsxNtvEXkGL&1747c z1{@%%wsYHF$Zfw_{BL_%ovLrMvnSkyBz>E9JiIdM~m!uWH(Z4Of$-V!S|#3^rWdH4Iwcp(OWY;1eD?M2+~ywg`r zK;{8ef~44PFLJ+=cK&Df+iAYYhHlQk`1$YO4aIftcTILo0UHT&vcX}oz0ZVXkMl?9 zUz_*$4w_W$R$I%uc{1-$zQ59RIb3Vm!KErt=Loc2Jgxi3{+JOj#CZZh2Q0{!57bwG VQMR_6nTkptq$U(sMVCKiTS(8=g={!k!d+V$?(;~I2w;g>h>dE=6i93GqnGu^1 z7+iY(?ANoJuELFe+giY2iCo;Ws?O)z@5bNR`nLYnRepX>5O^8>TJgM15Ti{}w9ewk znKz-j8DA%c9N)GEOr+*t% zY5kmE*X+$+&wI5{yRCD(+QbPU;1|IUb;XzEpIN^Dkp1SjvL=oJ2s~Z=T-G@yGywoB CQe^=E delta 227 zcmeBR>R_6nTW`qMY{0`Bu=~|sC;u=D$uB7{?;M?aDuCnsYSDv}CHGC5V0%qsb)I32 zA{fZ7d{=7yJEWvKZ&?c%sMywAG2Zmz+~4o)=XXBe8ZIv<1Ok^FFCU(M%E<1E&h%|X zFHcXI00wM6{N9!81TXqH9qV~MUC4$s%T_UC24xt;T^WB>wBS3j3^P6gnPbQgJIeWyAmT4bldN4_K8$r5L(?F$VqA_7h+L0#8>zmvv4FO#qa8 B5C#AM delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0YQVgzWFBG%)~yr>mdKI;Vst00V&xJ^%m! delta 38 scmebCo1m+>;s5ytX#>LttjeKM3|+q%gMMoJ2`~VGr>mdKI;Vst01Rymw*UYD diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-hole-Vector.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-hole-Vector.png index 1460b3271f545a04c8794c7a6cf70b4027e315e0..a5cdcff224371d8a0d41452399d01f360857fdc9 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-key-Vector.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-key-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..0dea1ddcafcc14f793d3b3594bd9a39302c7f696 100644 GIT binary patch delta 39 ucmebCo1mlg=lq8GjOGbR2e{o;{GgJzmL73^ywnCs{At_lp4tJYD@<);T3K0RW4x B5Ssu1 delta 47 zcmebEo1mg4>gnPbQgJJJP5O`X4bldN4_K8$r5J8hGEO>IpZbUa2s~Z=T-G@yGywpj CuMr&p diff --git a/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-wall-Vector.png b/docs/games/Partially_Observable_Bait/img/Partially_Observable_Bait-tile-wall-Vector.png index 0dea1ddcafcc14f793d3b3594bd9a39302c7f696..6985a08783e70b5bd12babc65a4dabf7d339a02c 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!&;I$0{0T`35>t<{FsQRLUNVjDm0gnPbQgJK!&-o4W8O;-t4sg4zGGS2jV7xTt?2If1An-(&`A8#$a+P-W0ESuUX8DFAyA6%%yBhkVL z(%&X5^3#0R?z^t*Yt!Dealh5Og(UZJcGcdG+Iz3hoo?|UX=itt1}8IH;sFJq-h(1{ zm+$;Cb8_-&wTD&RZpEAKJ_rzkxW#~@S#ww9*?kx1o)crd;Y1MjqcCjm-%zqoIT+> zWv4vW-xYn=^|*A!UfJDo?-ubQw3l4C@Tz?}_q&rQqBj=ZHZi+3dHDyw3cmjvX~%NIrG6(T2?4{5!PC{xWt~$(69A(ti9P@T diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-1.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-1.png index 353e977c8c40a253795c4eb3180e514594fc1616..67d68b4f64d64b614d819ea10d79f5abe406609e 100644 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7!x;TbZ+AXFFSoh;o1Hx+3^^E>VFZEx4Z`=M4TdzEMwb@BU-I%l+-5|2+KrmaFv?g!zBYExu=QUwymJ=ImX$ z>n<9om)~AjEhXc1sKF6plZA`*UDxl@5pNf@!W`A$IQ#XE?sMgb=GP_fsr7re{pq&u zG9*jS17-7W{_49WvHm5C-PE@G$+wa1Oz4s;zFg}0zVhBv+u4g`5mx>9su?v8=xMOk ezyP?gTRd9Ow&ug*+4jI-W$<+Mb6Mw<&;$UaW}gNC literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfh7#x;TbZ+A-MZUj zw>W!3Gy$C`o_{#D%;y%tPS_x@L`K~Z|{i=`-N+Ne-cd)VB*`)wMiqV)y1DVq|4*-SzrOanI_dGfjZbvmhuz@iQ%yXez$4MZ z*lZxPXw7`r?>-leT&<^o_y~iT*&vD@I!FI-Ew{VHCw_Nb9AVGkQ=g@}M zzOT>hZk7Liw~BG^`&;F*mSWMy=iXM!tx7(Z{@ZZ><+}-iLO^v5juHkOho5!-iu^K1 zGiq5z_aumZusAbY;sFI7u%Ul4h4V||!+x%RS)KEKM*cfCB!y-6uJ^^O7XL1v{2?g) zNpBvK)Hi!q|1-Ouem^W%skJT5CcMH@?c9oADqFEEdof^hBowAxsmV}CA&?7Y-H z)%o*#rg^T>2+gTEwQtvA}Y?&AdCi(;y9SG}q4nzkF}WuQyHyk$Oh!e~$R#0}qo P!O7t1>gTe~DWM4flVq8% diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-3.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-3.png index acf4b8138d45417f0b03839e441ffce317c5c258..7e6617b0c8bcedc8d63b99df9b514ef09bf1ac91 100644 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfjUgx;TbZ+CRasUFNxd-sbMwl_#$*Jo0At ztIgti&Jnz3s)+{_Aj zzt8N&R|9@joUhB*O*FkR1!4zK<%jdSC1SGMt$wFxPoG?=N5+H=+i)%oRpKeF{|0>YB!;4goQKdnFaywZ7R6v$~%Z3Zu5SN|#7xMKU4 wdAnWjZT2s@jj(gW*WfK{V6FuE^}=fPKZgw40=m;xfC0?l>FVdQ&MBb@0N)CsUjP6A literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfiJCx;TbZ+;?)1~^mEqw#kcg=`63%U zW6|}M_iE%)y_UbYC-OExh>hn^1IXBB&8qa^l4Ae)_qOdw3EqO_pX2ivrJ4f`1uFzv caBnYzx|_|DsINJ5fFa7@>FVdQ&MBb@04>#%(*OVf diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-4.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-level-Vector-4.png index 4d8b131566159775bb3edd5f3ef82ffbe320699e..89b49a01bd5eccf48091bc7d8d3aae4a4df4eca5 100644 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^O+cK&!2~3a*3_FaFfclKx;TbZ+YjC9V0ETwPyyY+ui;jkDKx-$YpdVSV+dQo~4I fsJDUEE^sgJzoeO>$hfl?7^n=Mu6{1-oD!MEsI6|1U$=u+5%i0Fyaq=lcDIO$zgCqn4`*JQ6L8&0wjlysmkdFJFJ!o@jPz zR*bG7uifUW3Bf`z(++cX{VBh=b>EA3A8jnpi{CXGV}9{d1mrI*8Z5{_wV-i?PjjGr@(AJRM52Q@9qdY_4jj< zj|U#EwNsZb@5?p4flzS4wfCQ1)pGZ$ulF7)TX%2MLs>gTe~DWM4f$C{PL diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-avatar-Vector.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-avatar-Vector.png index 449dbd771182fef12e06a8a184c42c601fc0d169..bc5c2b3ee088996e95dd51d51f5d9ef337bf7373 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VWXjOGbR2e{ozmvv4FO#p!v B4;{GgJzmL73@;{GgJzmL73@gnPbQgJJ}C-KMm25AGs2dv7WQVi>?81qkeYAj*^0#8>zmvv4FO#qEC B5A^^5 diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-green_block-Vector.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-green_block-Vector.png index c59c09c03c6c4a719238e15ae4653fccfbc1853a..1ea637a7b36f451fd81fe5aae10ef8514b6d8c15 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJJ}=jZtbb^}9$9gnPbQgJIe<-+gt4bldN4_K8$r5MWE7#+Q(baEJgz|+;wWt~$(699w1 B4+H=J diff --git a/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-green_box-Vector.png b/docs/games/Partially_Observable_Clusters/img/Partially_Observable_Clusters-tile-green_box-Vector.png index bdb0ce86ae6361287f80a61213cd408e7d47d1f5..cddd4d04ef230e1c5b98578b3937de5fcf9273db 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJJ}=HGlq{)D6iiK)j}7{d7(f5k^sI5Gf%r>mdKI;Vst0Bcze A=>Px# delta 47 zcmebEo1mg4>gnPbQgJIe<;KtXjOGbR2e{ognPbQgJIe<;KtXjOGbR2e{ognPbQgJIe<;VKt4bldN4_K8$r5MV?868vhEAlY_fv2mV%Q~loCIEop B4*viE delta 46 zcmebCouHyA;_2cTQgJJ}=HGlq{)D6iiK)j}7{d7(f5k^sI5Gf%r>mdKI;Vst0Bcze A=>Px# diff --git a/docs/games/Partially_Observable_Clusters/index.rst b/docs/games/Partially_Observable_Clusters/index.rst index d9be2d8d0..f62e1fad2 100644 --- a/docs/games/Partially_Observable_Clusters/index.rst +++ b/docs/games/Partially_Observable_Clusters/index.rst @@ -1,6 +1,12 @@ +.. _doc_partially_observable_clusters + Partially Observable Clusters ============================= +.. code-block:: + + Single-Player/GVGAI/clusters_partially_observable.yaml + Description ------------- @@ -84,6 +90,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-0.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-0.png index 730e31b78749c9ff206edae7b70ed535377d7059..8c6293db779c24739f59b3510141df44b8a18a3e 100644 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfgimx;TbZ+Z#dVcbhs&H3hwcz@end-vDxswBOe(z2>P zlV1BF6s&HOZkH?Ps(f3(U; z1E3wA6W~^YR6t#D$yIlMVZ{>v>;L$&&;7I5B$GHPrX1>Opb_Vy@?@@Es1XW>K7O|ZTWTKfvqp2ierrYgOtI$# z1Z#dVcbhs&H3hwcz@end-vDxswBOe(z2>P zlV1BF6s&HOZkH?Ps(f3(U; z1E3wA6W~^YR6t#D$yIlMVZ{>v>;L$&&;7I5B$GHPrX1>Opb_Vy@?@@Es1XW>K7O|ZTWTKfvqp2ierrYgOtI$# z1}}BeZo%a%^84i7E;~HlxnIS_B2!q)dsj_N*(D5C0W#dgR_(bqpY6NNGt!@X%zOuP zCs02~7RG(GsqMGL6z{4pzy5Ykuku&yd-+=-S;BLfHPppGyDmF_vD&|C$I|(}bL97} ungDbeRI^7^DQ|h-l!-8(0NwiQ_Ps7G!@&6kdw75W%;4$j=d#Wzp$Py+tD2nv literal 383 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfh7#x;TbZ+%OhI8nsz zIe~?95=i&d_1fhxB!bgSVi)b~7IpsCGcQps@*Plv0#F`k^x4@N=Pj<(yQNKf>IV~Z z%vz`MHX0jE)+uiSngg{5mdKI;Vst0M%2MIsgCw diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-3.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-3.png index 6785f507e6e5def2a55ffd2a66c4ecc83063959a..746adf189bf7adbf70fc244639fc23f398957247 100644 GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfhh>x;TbZ+oblc_ac0U+VHJ-i6^{ulo)b=)uKjUq`j=O0?Jqf=cAB&OABy!=$RWsUd${+fdC5 z{Up=AXNLdHr^{zTYyg{d>DTt>$^XKSR$0pie%Nn$253LfEU+tsc81j)Q}lbe_wUy8 z;_tp*Ic6){^ZBwR-!yBW{SalD!C$Q{KTVFi+iwxkUUL1x)`S&vWHN!iaD@88?An#r zy^rU21L1^BzH{fzeYxNKMSPQQ~$J(nrdYs%Lp2d#IrPGI4jq`)bp z;wZEv$Jf3uzRl*f=|ju+JCu}9mZ*D7VDX%w093FvJL~)9z-Ol}3VH9EExJBv_o8SC z&t=w*LMk3jDj-$&uQMHw)}LUQE#p%T76F-hY2&Z0#}?kz_a78XUGeOnJc{B6eW0tDN#W_wiPGHI0vg}XM->!#kA}{4m*||@-zj*j=MLTQtp`b}WroTR`hq~iF?zVvqCiSHtdZbtU&)?3!wC!L<);;)^!j2oJ)Rjx%C=M1}X%N_f_`d_+ks9O$J1~T~4 z!K|P4GSB>ye|eVk=B+&!6LNpmhC4tRN1zvgs;a&{lVwLZ1Sq=p1*4+1L1D0bSUxb& O89ZJ6T-G@yGywoP8>Scl literal 393 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfayqx;TbZ+<;u_K{)K1id?!8jo1nlsNdai8W7fKTm%f`WF!Y?j0u%vTwWRGe z|BmI+Wnc3q!NpWOZtb;wZ!zUp>XC#=Knr1x(z-LRT&C{Flm91dTQ=^_Q$rZjv}D!o zk0&QiTy`d_>*w$3dSBjlp0?cn>rUN{N%Lf0BDuXUbnXAui+=z6TsdbtQ0>N@vNKhV u&jmXU=z*pyG4J15oKS%U0?_U+mHN-msdUc{_J0WsVg^rFKbLh*2~7a^PoahY diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-5.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-level-Vector-5.png index 1ac633090896a3941031023db16d4e0b24d0bde6..07ee11cff2f6abb03125ee3a829d5e7cc10f2255 100644 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^JwTkt!2~3$^l~0BFfitNx;TbZ+dfXjlKllwHCq9!)A96IeVaw5=}P z(;fHdU8m~(#`ndRpSZl^#49Y1U*0S2xy;&82&(R-LulQqq|u6mpI&X=4b$|phTeYN{rm3x=l33;>(2fY zxb^b>94V0LK>Z*!ESWoQb=dFH`{+3#I?8Ovw22UxfwVdXeQ%2?Jdw?1diS?oFH||$ z#)7Rs{WAY{+?Kf4qXIXGbJ8yJhh2xKnYOP8a5!hc#*ncth X*z3P+=R08r41WesS3j3^P6O= zw3JPZrzp;7RNdq}{RK~KlcWSwS^~#s_ImxS6|80F{%++`bw3}p*2HBeU)5*37$%P< z7S9PRoRbtdgR*l>4_`;*R z!k2&fi%fshv9zYubJJa*q@$3EW0Q)iv^IJc#;UL*p+TShy z#9v)E6{G>k7gF&6YB+1U@6Fuh*Y~{5|5egyd2K%T43*n+6=3p9s*}6-mrM!X6qPi8 z&AG%$V248Vu8Nzi&i8YsQ~m$5i9pvQ8Tjq{#7CbeteIvp3+yDQ2eYm}>^vNt&h>TP z;><}ognPbQgJIe<;VNu4bldN4_K8$r5NUJXG~1&Z#%#M1fH&bF6*2UngF1w B5Xk@l delta 46 zcmebCouHyA;_2cTQgJK!PyDF{b^}9$9zopr0E*2J AxBvhE diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-boiling_water-Vector.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-boiling_water-Vector.png index b777829b082c9dc6ce82bcaff0b0e2a7429a52ef..3b12302023c538451d858319d02af373c79e542c 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PyDF{b^}9$9zopr0E*2J AxBvhE delta 47 zcmebEo1mg4>gnPbQgJIe<;VNu4bldN4_K8$r5NUJXG~1&Z#%#M1fH&bF6*2UngF1w B5Xk@l diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-lock-Vector.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-lock-Vector.png index 34f07f038fa615ce17cae226d1e99d44efa4e0b5..81269459b38b16ce85e5ccbff1159e393735adcc 100644 GIT binary patch delta 38 scmebEouH%m=l?85{)D6iiK)j}7^1&3{_6`rTEzeap00i_>zopr03fOk9RL6T delta 38 scmebEouH!_^8b7TyMduWk5@7iL%su})ybz--V8wC>FVdQ&MBb@0QbZUJOBUy diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-raw_pasta-Vector.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-raw_pasta-Vector.png index 0efac0d902f1e12d2a33b5b665b319814988f7c7..fd002494ae89d2b695b130d855111239cb7a939c 100644 GIT binary patch delta 38 tcmebCo1m-s;rIClX#>LttjeKM40G8S6Zg$|{G0&@JYD@<);T3K0RS3|4Z{Ec delta 38 scmebCo1m-sqrQdFJR#`-x7#Wc25k?<>;Fz0Jz)R>Pgg&ebxsLQ01WXBdjJ3c diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-tomato-Vector.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-tomato-Vector.png index ebe2993c51e189337f6bfa4c2d3a16ed25b6ec68..55f2af5df80a80919a29049e6eb10329c908945d 100644 GIT binary patch delta 39 tcmebCo1mi^T`ZA;OXk;vd$@?2>|<(3(Eii delta 39 tcmebCo1mkVV)1D{qj^Ho0dBWdCJfrVjMozmvv4FO#lT747dOQ diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-tuna-Vector.png b/docs/games/Partially_Observable_Cook_Me_Pasta/img/Partially_Observable_Cook_Me_Pasta-tile-tuna-Vector.png index fd002494ae89d2b695b130d855111239cb7a939c..34f07f038fa615ce17cae226d1e99d44efa4e0b5 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJIegnPbQgJIe<-_mu4bldN4_K8$r5NV2F(&St^Y}Rf5O})!xvXgnPbQgJIe#p2U^M)QQE1Ke(_Oc=Cz8LuxWbUn`i1fH&bF6*2UngD)Q B52FA8 delta 46 zcmebCouHyA;_2cTQgJK!&;MDB{0T`35>t<{FhqZ6{MQ$Lw2A=;JYD@<);T3K0RWdK B5eWbQ diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst b/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst index e48a5b551..a4d6a69a4 100644 --- a/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst +++ b/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst @@ -1,6 +1,12 @@ +.. _doc_partially_observable_cook_me_pasta + Partially Observable Cook Me Pasta ================================== +.. code-block:: + + Single-Player/GVGAI/cookmepasta_partially_observable.yaml + Description ------------- @@ -93,6 +99,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-0.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-0.png index 3bcdbce0f04f5ed34d081d6eba06b43378ea1e58..a2099d40e9152a0ef2959039fcfdd9182387a6d1 100644 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|`(t>EaktaqI2fgT03p1RMe* zb}q^B);pzZxw5mwU|Q3fgCFjh-b#6^Z_(2cd_HW(jrq|E229EjwBf*+q}M+mhs%DP zbZ`6FXPef24_@S*wKnSDwhR`x1SpC-Sav`9ym{cMUH5`kiS`_TX*OWmoLXC8)B9>B zSL)fFRd(k4?)~4yzJA`*4NbWhIeHF2(VnKao&QU&Wv(y3WPCKrw(j5hH3`usFhe#R z@L7D!zefIZbvw@zgW9w$xudgT4bwPk9jxwfHLq*mj1t!97D3m-8i zZn)!pUd~B5y6KtS=a)Of!#3zjH3~|AQAEPBLc^1*s!d!hPrq+(efN7??4NU6-yU4O wR*pF?@Qm<%lo05svXV|^h5HIEaktaqI2fi@AptL>vNF z>iVz^x>5*L>hn>igFJwB2`OP37e^)v1$GyWtkNJ@Dn3d-7Ip+Lt5MsjRmbAvwI)wSM=9&hsau zKF38K+?D}#ky}Ee=i{VFrms~lc9{BiotR8^bDRNFFq?Yh#>nFNHE!>wism33CN{$)i+j1sUiZr@q849Y_fvQOFTVBP w&wN|mfgE%Z372-vNJ(Uc`xO`_I*+&yO?2+q{_JohFcKL&UHx3vIVCg!09oqC<^TWy diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-1.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-1.png index 24d5ab3c2bf2c530216d22eea623478b4f4d8d4c..5b6459ca559882a3ac44c7b0d111c4470d19fd76 100644 GIT binary patch literal 484 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MB>EaktaqI0J$GpP|0uF(} zKiz!SPnoxJ@1$DwU1zfbx|bdMaL=sk(GPAh<=I77dOP>-WMxt|fFkZQI%V4vSo%MG ze?RXk*EUzUgz^U=smW`C78#ygTo+e#@%T5{*igxjZpS|_`*C?*eG2RCMS>EI5X71$ z)$4ifw-|3=*3SF#c9-uh>v`@Qe)jsBglH3(O&bpQEH*r8C2sAiJmgI3B*SDfc4OX+Q_=X*#CD4KxQDL_iH`0`^*pX3CA0Cli6O$KdJe=d#Wz Gp$PzAIK4gq literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MC>EaktaqI0}$GpP|0uF(} zKiy8woMo(klTSTn^e^oE&E_*LZ-V2a1@CyYDJX98A^z z|L>;q9M`CW+cH?(5+JB?rd#r)QrrBCPjCNKHjkCRcy->&fO!@7MB?B6>|T=)ZNj8% z07e@Q_$fzMdm#&pe`|(_|NVm<(nR9-Y~D*KBHW3w%<`}mwYk(iFRbF(qe@;P_%kXSFs!w?6r{AQS+z3>0b3|5f)H$X$8 zzuHd*sqN$WjuG;TLA4 hB!bO`8PD^Gb6&YcyH&@XxxkoX@O1TaS?83{1OVcxzpDTM diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-2.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-2.png index c6a15f59145d60b5dbc726edf4334d1103cf3f6b..d64e46d20e36711c295a6be2a39a16f3e7bfa03e 100644 GIT binary patch literal 484 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?MB>EaktaqI2fi@Apt1R4@o z7zOPPYJ26i#rPucxh}si9RK!RU*R=3{zJi`GWpGg8~E)T4{-EA5mRy3@yh96ZZ7t( zk2cvgd$azpdp&mZ4=!I^8+~wF28&w)6zyy7_Pn<1rO(ugFwyw;{*V4dJxDT& z$Kd#m87ZmDjN^5yc3t+lW4Qg?yvrKe5{;~IqnGK{`dyp1d6n(j*$X0(-PLI6cC&cX zh9;mLaMf{_+%0;$&Zm5pyk~!7?#W;L^M2%^yHcw2H~Z>y#i4tRH@>P~n-Fcnq-+32 z8xF`Uesj0={SQ7zpbH>Q2bsL=!XeLVmG2q1fUE+c2vEpyEmezrZxQ(Fe8vH@sO>0H o-%36nDPIE%J~5z<8NPoQGdQhYXtVTY0Ar8A)78&qol`;+0L0nG*#H0l literal 473 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|?L}>EaktaqI2fgSjmR0uF)x zTVz#DF9xqTxw5k)L8N!i$64Rkt-i{(ukCP$!6>rNu=IgS} zKUT_P_x7Xh%W~sK61md9Gn--;2}(3V(GS6-8vkp1-ug_Pf8^u2-|INt5?I7wvOA~l zQrF%*H?sJ>@%cS-AN+`!nJ_(r1*igy8gCwZoyi`vHE`9h*}faRO_-Doz-YsPoX2UC zr`|kQ(<|q3bzSk%%Nw7Wonlem0CQWb0d;1SZ%8?wwQrB_CHq!Rv|K`x|#i4tRH@+%IbIam4 zcU#~8;Byp)g$>A&!5NR#viE;ym<9C<*v~VZR{CDE&YSY9V6RwW6EuJ#Kq|yaKOZRv jxe1K8fhL_%s$*Pp+49YitT~#%C}Z$+^>bP0l+XkKnS{iR diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-3.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-3.png index bc65ebce3dd15ee57d0658a8d8106e08554fdcb3..c8d1e3ffeed5fb3c9e71a7fbf148ef53efab2ca0 100644 GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i)>EaktaqI1!gT2iL0uF)J ziV&k^gn01lV_q%vcdb<#dqI)GPUbz*fZ_O0XetKFq+xmOwzw3GG8BMYmk<998 zo4sb%Tc16w+1tNoe&4(ITb0@k!CshcItGVl#5CpaJace)c9ajyCT^fpeDuvC?Y;WS zLc?qHXCOO+`;0-ES!8A3G;`JOl_~44Pp*2q@B7q6)d#nI>C>K>;BEqQt^w11$GF44 zd)EEfw<@l_c=!JkEXo^T7WW)ja@j7n;^^}qP+K7~KEa{0*Tnh%Ol%U2fcehtf%K|d zDRV!c)BJ5JtOIi+&?ugjuWTb_L{_@IZ;rp5gzRmO8g}d1Iw<}*<5|O?x?L|=Drkit QFrpYdUHx3vIVCg!00~aDLjV8( literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i!>EaktaqI2fgT03g1RMgT zmnyH|%u+J7G;P(6QS&%vQFw3nqSzZXz5I(OoK;=hSr{9`)F>zcMXYHe!GAuPre4o@ zKCM7Jvq|nS*XC=6sFew{= z(S`#&iyx}3PO}!yKX~hH?XA7}>-Wy%EOUWM8!&O7F;JWSAa|uRiDlG-?CX2^?pZ#Tf+ zwbvczSHJo?^Y&j?HHk)6h{-@w?d`YQt?qy1=5rz}lW5Fbl{NY5>;E4Un*<|Z&TxAm zyy{lU+t24Tf13*HKs*F?3(v||mXUungMQfj06H4sTcEv%j;V1A!vhTH&CSC40%C^# UH`fL`0;7n*)78&qol`;+0OvBQbpQYW diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-4.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-level-Vector-4.png index 9069320cd0f656668c37de33cda8dcfae34c047d..7c90bebb2c9f8c3961529c22dc00cc76942acea0 100644 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i%>EaktaqI2fgT99y1RMhS zZ%uOYUbt+Dscv%g^i@iJNNQ_66RU9jsu$qzpkD4uo7h)~^3h zWUteg*DpRC&n+m}-^y9m>6X9(MGuTSKH5e;_Pw_J_s)Mh#cIa$O%Li_L*?hP3>%QK3pI#T5AB5`B0olB|!vC6n?e5ty zYs7%sW+Z#r#=fwPRD&210hW7~dTY=6dFA%u&;GvnTz8!X;%;uR^*&d_{uZQ3PrVs> z_IGT4jXPKa$RvrznX6UpBcS0hc}vb@(Z5y} zyQcq^Z(40uu69FJ3hEERh=fxW8&3YZb*%rk?f!W$19QWtRvH!Fn|GRJw+l2B4Vbun nBsZ3;AA~v&C=7JrALjdNG6r3@r>buNMi+yptDnm{r-UW|shzm> literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Xg9%8M95yv)U|^i%>EaktaqI0}N58`c0uC2> zKJ}iOS)^X1ddYaEZcfaSiLWz0-80K-{*|il=GRgDTDr9M2PcbL0u+hO(7N^GQ!@Ad zi_%BzW~tZJ_iDM6Xx|X)J;2ceMdbpIjUyj>Ub|emuKx6&Ni)w!9o&`yH=$8;ajM;$ zBiHh;&v_k*D)IQJ%Im!4>+HT?yjs^H3NwyL*??(t*Y}ja7x}d%8U?|K73iL4i-P>c ztNf=y73vs(6n-|#y%&Do`ux;N`(Jy0M;(MX3M8X!5WF?i{G88Xqs^--zkfYe4sj9K zz!@uVnf_lB8oaRTyf>O-lWzju^nTLzCXmGu&`_8(&399h{Mrn z>Yf=XuJ_r)&mSpRCV%x@O6okHIp)tdHRWD}2N6@S+nlHOcv?jvt_C@8!+|MZnD2QS Vd_K6^mlGIW44$rjF6*2UngG*D!88B> diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-avatar-Vector.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-avatar-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-exit-Vector.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-exit-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-trap-Vector.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-trap-Vector.png index dabd04345356dd4a014f1bc28dc1420c1c7a6afc..f76f390fd0b4e8d0d022acd2d642d131e07e7c90 100644 GIT binary patch delta 38 scmebCo1m-sWB#!QX#>LttjeKM47=(XE4QhuNiYC`r>mdKI;Vst0RNE;3jhEB delta 38 scmebCo1m-c@%wy(w1MFRR^?DBhMmtCEB$o#bTR;er>mdKI;Vst00h(w$^ZZW diff --git a/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-wall-Vector.png b/docs/games/Partially_Observable_Labyrinth/img/Partially_Observable_Labyrinth-tile-wall-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 zcmebEo1mg4>gnPbQgJKU=J1F4jOGbR2e{omdKI;Vst0Nq(nJ^%m! delta 185 zcmcc4c$IO2O8p#97srr_TW@b0@*Pp&VR3k``Tzf>_w0U+i&zYA@0OUE#Hw^PR{d#s zJF5g3{MZ|#eRyuogee=Xtu7safNGPqvyTM_KYlqcD$3|wSPn#`#E%!tOv_h#>7Th} z00AHV`DQ<@H7H#E)PorevZIX7KUv0~ns}|P5dz9R{@AjxK|q~G{f0A=le-(OW-tJO Mr>mdKI;Vst0DB-zl>h($ diff --git a/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-level-Vector-1.png b/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-level-Vector-1.png index a06cf0ecbc086a05d3916470a7e5eb50ef5c9e06..2482c4c17068e1f8011a76f906140b61c4865732 100644 GIT binary patch delta 206 zcmaFE_=<6YO8rJp7srr_TW@bUave6{adp&w@-I9rFG0U$!{jr!r#Tr(9A4gQShQk4 zH)F;HCSIv64$WdOf_Fccf8L?nFPdAu^?mQN^KV>sTRCu>WL)3?$!^y>d{j*9%=R8} zy-P7QH#^Hp8^oY`|K~}DZM$F7oVrc-@F^wTJrjl9-@andy|BPhnSnQILBH-Z`U zK5oX03rxIHTO69jUhKbiTtEGYy1e$OZ&`QErRvkvZ(U`KTHx61#sF0Ceiu)=adxW5 z+wC64b27r)9h%v6FDzh{+LHR?-nZE{qB_!TmNl=py1tsZaJ45h$iNJs^sfV{5#JNm zzd7E2@lt15DThf$BO5Q!%-4of{xJ6ngP0(NOuWBdZlvfPsk#|FblK6`u-a Q=Q99-r>mdKI;Vst0Dv`3;s5{u delta 190 zcmcc4c#Cm@O8r7l7srr_TW@bWavd<>U~$m>`+xddew~BP&Q@{V6BL`g7f6N{hQ$`f zD1yP;kn;aKjm&LIwsjtV01>L4DHpe`q9l6HDsQ=&x%o??oxs4#z_#a;<85mWel~ti z5XgM|#o#3SJ-G>B@Nb6P9$N_;a}^MHnYi(mYy0SG)@{an^LB{Ts5aK%h7 diff --git a/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-level-Vector-3.png b/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-level-Vector-3.png index c392c21ecbd89ef2fe3c117214e9e5a04382c820..fdf60765c01caf79a47be624d47cfdd8796cb952 100644 GIT binary patch delta 169 zcmX@Yc$9I1N_~%~i(^Q|t+%%vxf%?3SPr=S`9J+Je^iD@)SSIyg%b`4nJR1fofc(h zlLCUO_jevYUL_;@{8#Vhi${(zgT#J)eUjq7=H=7wnW4Fo{24&-qPOfw;nS(=yddy* zx?9gz-*r~{p}p)TK=8$}_r|N!$HcuJGlHa*{yOIVyenQ~gz9l5ntccx!smI8ubZ{5~>S^I3+LJ;_W zv7@|p=JE9o5XE~#UTwC|{IEGZ*dV9y4tHFSW^?=Ks z|5IP{MrCyF_uIC}LZ#cCH_fGc?yHWoToPdLW2eX7*mX z6+ Q59n70Pgg&ebxsLQ0JCLY*^QNb8zUK+!N5Lz|Gu@xiz=j&Z699MRxXu*fFJiK zUsKUdU8CRkRpprnL@D$Au#Z=+A3oU|emmOo(gDK2{CbMgzIn?oB_=??gRe_M!)>?M zsV@C`_Ovz3g!>**;hRF@y)HwQJ(wQyI9~48rAgMhP;GZ7Pve6+A)n8Vabvu~hf^wX RKY;#a@O1TaS?83{1OP=!eR%)? diff --git a/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-avatar-Vector.png b/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-avatar-Vector.png index 758055b8f4658f1313667b0074ee7ebc3484227c..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 39 tcmebCo1mkV^5g$}M)QQE1Ke(_Oc;tdnS|``sWdSFfv2mV%Q~loCIAvc459!4 delta 39 tcmebCo1mlg=lqBHjOGbR2e{o>FVdQ&MBb@0PBhih5!Hn delta 38 scmebEouH%m=l^_0{)D6iiK)j}7)scggo>_s-DCg)Pgg&ebxsLQ00nytr2qf` diff --git a/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-box_in_place-Vector.png b/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-box_in_place-Vector.png index c0ac2796817cbb2ec5f405e43f3a93bb6196fe93..758055b8f4658f1313667b0074ee7ebc3484227c 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy delta 47 zcmebEo1mg4>gnPbQgJIeCE>^U25AGs2dv7WQVjNbjKZP&F1s-Rfv2mV%Q~loCIEPS B4+8)I diff --git a/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-wall-Vector.png b/docs/games/Partially_Observable_Sokoban_-_2/img/Partially_Observable_Sokoban_-_2-tile-wall-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..5423baded82d810b8792b647a8af95a95818daab 100644 GIT binary patch delta 38 scmebCo1m+h@Z)@gw1MFRR^?DB275h5;n01T-57wt)78&qol`;+0PtW7v;Y7A delta 38 scmebCo1m-sYQVgzWFBG%)~yr>mdKI;Vst00V&xJ^%m! diff --git a/docs/games/Partially_Observable_Sokoban_-_2/index.rst b/docs/games/Partially_Observable_Sokoban_-_2/index.rst index ef1a9055a..267e3f9a0 100644 --- a/docs/games/Partially_Observable_Sokoban_-_2/index.rst +++ b/docs/games/Partially_Observable_Sokoban_-_2/index.rst @@ -1,6 +1,12 @@ +.. _doc_partially_observable_sokoban_-_2 + Partially Observable Sokoban - 2 ================================ +.. code-block:: + + Single-Player/GVGAI/sokoban2_partially_observable.yaml + Description ------------- @@ -84,6 +90,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-0.png b/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-0.png index a53b664ae1dca343bb0c7810b4aadfeee943058c..003660de487bafc23c4a67c64d4b3cc8058396c2 100644 GIT binary patch delta 233 zcmbQsG?!_DuERyn76So?gKL&d`;ugOEy?s-lJUl7jSPXad*Us>$e1al?^scN>9ubj zBU|DD1s;hO#%6<>g*8QccDV9yoz2!gX=0&pJy47%c;DxTE3zMF^e(;ZdORxndF&w* z4raKXT{aWuz3Kku_C2_A@$E$_5OWd)*mw>#I7%2m6+WKw|HsmO-#_olgxQoJ(DvWn z_58}M_LbiGZ~gU_ArwnoS^S~?B3M8UYEQ{VW~Ms9>|go!^cjG_)78&qol`;+07T`*}nJdMc4ET^RXzOj=QqEKf1wD!hnOBE%AT?&;J11Uk9Ja z#a_vivYFDt*lds>z{Yb3Nr6PmL*1*!>h9}$7fYUw4ZmB*S!=|LP_Q7nOD12nHsjul zZvjH+I>E-ZT@N|_+q?MWv5RY4GLZ}~y|E}c_wDMPUv7ra->E(uNw36$4|_8%EAU9b d-1BRrer%cG-|tM%%Ygo6@O1TaS?83{1OO(tXb1oR diff --git a/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-1.png b/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-1.png index c85c5cd9d901a12b5aa8f477bee22690bc54ef8b..8d09d8bc337b675807acea73bbe3090460b1aa4d 100644 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^(|~vZ2NRG?bYNm-U|=lrba4!+xb^nVY2Ic70S8C3 zBHb@erdOOyUmiVc!K>mX==9Iv{V7)CD0v+PuQ-zu{eFgp0$hqtiXu*mE-NN_t6vZA zU(vitbIU|7xcHoHCJ$%5%4c4?EP$gmfI|==OHF4}q|j|)lm%Kp1mR>rsWi0f&og zCP!WBz8b)N^#=FZBYcN64j*XyzP^ItUJjq6>BZAwQ+7@dZwTOM6<8?1wNOAQ<;08@*fgR~*GMgc>eIC&eq8 zvVY#4nf77g`^xiYMWQ?qmfCG@-fb*C*Kcoe$~NJx2yvy5Sy`uc=RNQN>c;O_tUma) z+3K5G^TJu<`)L0)wZkpUXO@ GgeCyTv6@Q& diff --git a/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-2.png b/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-level-Vector-2.png index c499a8fe15219cde54bd1406c80d8f5959ce4462..0bfdce025c4d41d760a4d30e5446948663aee9eb 100644 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9<2r;B4q#jUq@5Aq%|;BmMp zl`2-2IP2V{%w68o45eFwU$^Wl?-dBW5MH#wE9HKJlYTUV5{n=cGX9=rf9?MHTYV>z zH_X~7ZX5nxdF@(j&V!{Q0*W012<)SB$$%~9>=B+FeJI+MT4ruprQfH_zItc-{WPXy zE9bN*I60u;5AA!B+15RmHGW{l)gj=*fxu~(au`KmH691oKS+^Kw0EA9dn|N2PVKRzsAe-f$ aaBtz1kYBkf?FKLu89ZJ6T-G@yGywqM)_oBG literal 371 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9;xr;B4q#jUq@4{|aY3OHQs zSu*YI(X4IL)*g$y(VU)^mkQ(r1` zas9#7w&zsuzl!|z;A%MM{>NWtd2-0PGYU3wAh45M;r8Tb1#TZq&C?D%)=+Q~P()yl zC3?S8#7i%2zI{vu%@U;+-L<<7AN%=Ky*o0aMZw7d1#ejTietw4Z&H&5TsS(Auo8<{ z*Sq%<>*9)8)R63f*!jWII?e1s*kzu=-fxe-6z>sVKC|UPp@@KDhX4XUDcqgRX29v# f(!ha?6SeaX2TT6_bxEom7?=#6u6{1-oD!MgnPbQgJIeWyX*BjOGbR2e{o^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ delta 39 ucmebCo1mkVGULa5M)QQE1Ke(_Oc>rSVqDVkxc37C5O})!xvXzmvv4FO#m9M4KM%z delta 39 tcmebCo1mlgCE>^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ diff --git a/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-tile-wall-Vector.png b/docs/games/Partially_Observable_Zelda/img/Partially_Observable_Zelda-tile-wall-Vector.png index 0ae9106602e30ef63317c132966ce0d9f4203e76..d8a72ac8469cd31b3e1cd0c3c146edacbaebc011 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PklEde?n4%#MEOf4DQX0Zy)VqjbQ)+Pgg&ebxsLQ0C^7& Ax&QzG delta 47 zcmebEo1mg4>gnPbQgJK!&-sFGM)QQE1Ke(_Oc>l&GQQGdmh^WeMsFPV3&@n^w z*CJNNj0;S>Qd=el$j1Cj&fdQ`x@Y^!U9Ph-!rL91*>o=~V3jh-Xaq4Fxj_uxC@=%4 z2go>o<*ZreyKw1+rC)CCT~k$Glr|HrPb>qZZ;PDG;$M3g)ud-Eij|!>QJBw>`wPQT i`@6GYHp+?H7OKyb=6$bzn)4F_5O})!xvX7zRCXC+8Oq@3Sja QR{#J207*qoM6N<$f~A>9H2?qr diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-1.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-1.png index 0ce964f17db99b0d7060fd8d1512a22e17bfcf7c..8de5045b96e19ffc012698bedb98d4746e4f7956 100644 GIT binary patch delta 223 zcmZ3-w3catip3XC7srr_TW{}PMwU+-3_sgnvvn&?$FGpdtm{q)RwawYG&?z ztEc3S9^V6x&HH-_vwpZDi=8F z-#j3Dy1Y&8)B3x0{)``Lj32s~Z=T-G@y GGywo7rC%ig delta 213 zcmZ3>w2oRO$X@7iThtHO^&I=ry#WF51@!mRF@FQyuf4H}t+O2Df7Z$Ke znPfDwtv%`A7C-l|j|oUg6iDq0OaHd7A1^Oejk+5)|2#;hkqsonZSq31J7>q1?>xR+ z)-rE#0GSFj1ju01g_`zm--qSZjKUKGlqD9_-{DPqD%>l#d}9#<5O})!xvXdoQ*ym}E4v@kT9hiDgB0IQ<zopr0AFx=t^fc4 literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1SGeyEo^0AU}W-iaSW-r_4e*V&SnFFhCqLr zlQT=y=AN8;j+aH$wDbM%=nY>AS^6fKZMrP|mBXP~EaSogR;eux&1||LhDk;vh~dZ$ zV(>-*86};(=WVTuR`n?-n_leWgNmAD%uZy@-6*ri>RHYrSy?8KQjl3$HO4x{-;FOK zOt^iAXZzv#da{pyNnii5cYY_r_`4|+`R#tqu5K$Y^ZU*NG6dufpdt1-8Sj;MroVV0 rkZ}Pbcx~k&`@6GYJbZ5c`?zcwzl3IRa1|>sP#8R2{an^LB{Ts5>-==_ diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-3.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-3.png index 34f645e64983e076d790d80e8bce2cbb375cbe95..b5c55669deba0417109ddf5bf0273dccfe5a5ea4 100644 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^6+oQF!2~3eI~|xA7#KM{T^vIyZoR#Gn)i@_fNLPX zmwSM)YoPEIlQ)rPl6nFsoWJ||VqnTkO+gpFzl*q;E-YY`GRbIU)4j03ksHL|jRG?+ zFo77&Vi^~hYLE4m=Kq!Ys~&2+`0uTbf6wFGcPx^XW#X0E;s8=G?cw!R*JJnIPrG*a z$}^tuEsLF@8hLLS{gLqcp0)7l;{AV@eT)7(ce&Z}>b5>Um@O~fc9rh;{dMMFU3BF!Z*OnpJZvD~5Ey&H z?VHa>Fr04H%f%_ly7xZY&I*|(XO&Ga`s6qqn#D3MEMS$|;?T^d3u2gLG=dn8+#m*T z6p-k+gvSoHr?1y`0XnJn7+8R65s6)uYR)N@7m?R&h4CPaB&?U zRI5qG?1%5=LjP9w|60CRa#lt-JIwSi|MS~yD~(NnYA-N>tZ5c|an;;8zfJAdRYs7l qCK(r)_WnxK5atEBjesMUzvou(QMuf-nmrU41`M9AelF{r5}E+Oa(9vd diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-4.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-level-Vector-4.png index 2c7275f1575724a5247b27e71a2a179b763717eb..5c15184cebdceb7898ca63bd9192aa58a5a8a8c7 100644 GIT binary patch delta 235 zcmX@XbeL&^iZ++0i(^Q|t+#hCayBT4I9!a|5@>B2V!U;7uELb4MbH0TKknGl?m1Dc z_F~t>V3qpsH?F1EXtK-;+5Ls&@7g5LG5tX)H8LOM_-#yx5;+B zZN(yGlF`VfdtrfZo2mB93%_gvw=8nxHp#fa#2dB1@w|oPv5B^Gt)u6j>opIJy0`!= z#Q`+wMP9ar>)+Dz8&X!4oWF2Q@d8+?k?3lsc1+4cW^&+PAyh5jz?*Nje8%b&c< zibV>f9;ha_O?mcL>%(WlGL1`0IZQytM=fx?EzWaX?(NKlon@sBU@N7zIQ;*frXf6; gn^92;sABqi-pvmdKI;Vst0KR}|%>V!Z diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-avatar-Vector.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-avatar-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-ground-Vector.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-ground-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-rock-Vector.png b/docs/games/Partially_Observable_Zen_Puzzle/img/Partially_Observable_Zen_Puzzle-tile-rock-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 zcmebEo1mg4>gnPbQgJKU=J1F4jOGbR2e{oF!Z||PwZBP(!a5TK; zq#Lzy?+u=^hW!!|M(w%2xwwe$mVd9wQ+}LfK`Fj-h7b!_1|kNBuh`T-M>c)I$ztaD0e0szukb4CCF literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^DL|aZ!2~4Zq!m+v)KgCv$B>F!Z|^wrH5>4_9;~_Y zJO1V!g^(sKi@DoV%{Qs)2N-3&IL*hhA>m*XE4LU^#D=i%Gwau#*_T%J@4AO|-;|S^ z+CXxu5gQUp_dWQ&`=YOy4v@_WBFpwY`2W_;JQA$g8%VyYJ@|j!oge@AC2d-xs1pG) z5vYs>OhU9I925d+2N}q}?T!AdE};1^WvkD#?q3yS(;z=Nl{XRS9FU1c2lu~lo4zopr0PG@nO#lD@ diff --git a/docs/games/Push_Mania/img/Push_Mania-level-Vector-1.png b/docs/games/Push_Mania/img/Push_Mania-level-Vector-1.png index c38c98e8f05e9b60b1a9f737a539718b6dd9131d..973bd4d06b9f8f45e84925f9daf8cf4158511b97 100644 GIT binary patch literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^zkoQ8g9%7BR?f6%U|?+Zba4!+xb^m~Pn zc8FbHr2Q^#m!$Puj;JXg8j6?M?>DS+_K2I3YAZQcI+ysIIObceflVamPGy+&`lf3A%UFTMV`w=(plbta}gFFwUwHBn#w z?flMF3C7GoPb$Ow+wQQ-Cfqc4&-$0=zJE=4zN!iAG`K4TJf6i|4HLG0Kl__rnq^pd tPMQPUZlE#Fjb}nc*C304yyo+cdCfh&+3J(IZGrK^;OXk;vd$@?2>{TD(=7l1 literal 447 zcmeAS@N?(olHy`uVBq!ia0vp^zkoQ8g9%7BR?f6%U|?+Xba4!+xb^n#>8>LRA`S;{ zJo#UL^WHbDWwj4NS8qFfrsau3(~6g;#AO~m7F8&pV0}XN311QW&gMIg?>_AR{qwkG zrR(RVZ=}CghP##fq&d7pQsdlOeP{l>tztXwOFgN3y=?Qoi5F!jOQIMNuyDhwS+n>! zH8|N zf2(H)i$9MGH2^zgBFx*)8s7I-M^!v~>v!4q?U#h_tC~*o0L||Mh6cnCC(Ru0Ia`y` z_rH9VG%dO0cnTXlxIhX*GOTk%dyoUE`OZY^H}ANO&KkVll%rh_j2H$_S3j3^P6>FVdQ&MBb@0PBhih5!Hn diff --git a/docs/games/Push_Mania/img/Push_Mania-tile-pusher-Vector.png b/docs/games/Push_Mania/img/Push_Mania-tile-pusher-Vector.png index 5423baded82d810b8792b647a8af95a95818daab..0ae9106602e30ef63317c132966ce0d9f4203e76 100644 GIT binary patch delta 39 tcmebCo1mlg=X^moqj^Ho0dBWdCJb&X8Q=06NIql$0#8>zmvv4FO#m9M4KM%z delta 39 tcmebCo1mkVlJMhvgS3I+16JiwDF%BzM&ZzXm)#hEz|+;wWt~$(69E4r3>W|a diff --git a/docs/games/Push_Mania/index.rst b/docs/games/Push_Mania/index.rst index 67c10ae09..0442d2e1e 100644 --- a/docs/games/Push_Mania/index.rst +++ b/docs/games/Push_Mania/index.rst @@ -1,6 +1,12 @@ +.. _doc_push_mania + Push Mania ========== +.. code-block:: + + RTS/Stratega/push-mania.yaml + Description ------------- @@ -65,6 +71,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render(observer=p) # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects @@ -166,6 +175,9 @@ YAML BackgroundTile: stratega/plain.png IsoTileHeight: 35 IsoTileDepth: 0 + Vector: + IncludePlayerId: true + IncludeVariables: true Variables: - Name: unit_count InitialValue: 0 diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-0.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-0.png index b12f21d53d1c52da046f524bd88b59c5d8202632..cbc06306339b0d78141abcc58b33cc20ecb8b34a 100644 GIT binary patch literal 477 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GsLJY5_^DsH{Ko7i_qL7*Yf zVDJB_A6fhsEAySGt~Xzo_g38Flv94PiNf=TLJbofJQP$wSU`w_Q$XlU;Zb|Jz3O$9 zd(sx^S30KEK9gIga5DD!kJ`uY%lBWub~pTR2g|ZDkaCEY78VZ9y7k+y2!EHouD9W* zuJxK6hG1)ifgGGIEbKdr^zY}px1Wh)O4U&?RBD{SpxB6H@tjv_=h^n9-m4VeQ@DG1 zURkwS!%A6@Mi6#lQe<>$V%n}Pz3BPJ!-uQl?wYR*_6z%S#!bawD>Kj>gjoTP4ov0k zD|@gwq|H|Pz!g4_X0QPuMUIWH_t;lnyVbhy$xgY_-O6vaH>nhdwF+|jyhRNHpQ7FA z&b_Ao+7Gg~-@QLKG|`1A*cuW#5cjmOOuqBxX2C74<{$gNJ-%%2SN(R@{X=`cn<_P; oJ6ovx-Ax8Jl)wTxC@R~Z_o~n3^9Sxa0Hck;)78&qol`;+0NW|6k^lez literal 477 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GsLJY5_^DsH{K8@TR}f`G%t zzL)>D+q`2CDm1z<@AL0V>D~9HxGb4i$=Gez#?Zpz#H7dw#ElaiJQ^k}IA;5^`p>`h z$ve`Nl8-;suz9{l>iJF4rysf=?MZHL77f1|H234{UCNCsWjQ!oSU?!8ze#B#_x&C7 zca$|ge)@wsJC%9Ht;PuqP(vL&6jUmvR6l)SQ{dh{buY^=liVK-)P%=kh=-wk;3*GI#p+XBXwZ zui+|JG1$rs^bN#E3MvjB0V&(k51$T`@TuD>|M14y`>ltrZ%qgL8WfBm9}5U6sJz(l mdLHkCK4cew!uddHEu+?x;H%l&c~XGU#^CAd=d#Wzp$P!P@xNjK diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-1.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-1.png index 69599dfe86b363ad7de525e330b82c27f2c88d48..c1fa2fb62f7e80e19b12b953a2def6feb5da6a9f 100644 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJzX3_DsH{Kd$H@Vf`G%t zGn4-RPyM|wK=+h^+Pu%Za&FIv<2iC{N7U{H)A}}s78WNaMMfZQoZ#TmFhSw?-T%ei zGwNPSteN>@f06gE5Py-i^?mCOyDffJeYHN`SZK!~AJ2AUnqH=$A|M399GopI z9GsIx*5yR(-2Qh@D3kVXm6U(^-%FL_%EdOFn<&W{a|_}?2M>rI52G8si|W^J-d+-&Xs Vi1Vtr-r>mdKI;Vst0LFy0 A(*OVf diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-2.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-2.png index 04921d5c297b11d53582a10ec96e3b83f2e6f7ea..8472b11df36c54b901ffafee8c401f9d1b290365 100644 GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GuRJzX3_DsH{Kdob&;fq-k^ z>L>r}D|czf`Yt%UddasrxgE{2@hKZ7TnqlnU@Wi4$idmd;=}~Pij5N(6djMP{=ef| zQE%V%^S&SX>voG>z5n)a`RNW8xwj4;3MwEhAjH8bAoOQn_`3_)my-mZhT7ThGFK~K zBgkoUt8s#ZM*|RpR4J%%aGuLKvDbB9@w-E{+S+Sw_^GC^V>Q~D-0Sh+A|I-?9wlen z-i3ac`#Z)bP5sF^^U{>+i4qfG27#<>Vd3E1R&BgF@p_+-@z$3;McOh;{w>yit|zne zN2?&%7mAEvUx1vT@Z{{7wv+q#f2X;wxga_BXVBh%w-0f-&s&eFQe}s<>wdWdIZGR3 zr)PgUTzYrn@4qfZ-`ha`ff)!3r+qBxW>2o=yy&laCB^oA@9F>5pZSZhdcAYq%?F=~ zrk$KK13E9ocB3r&fexUbz(Eg_IdDP!)3VJfXCtD zt{?xx%gS5xQo00+CVk)5I;G-Qr{W>wYgLS%`Q8jpOp1*Y7=YNpLqWyCBOp1?PJY^p z66HC*k^h?Pv$r1F$|as%snobumV>i}1%!b*7@eBfHov>H_@zb1+25IRI{$KWp2;0D zl=oNJu$39ryd?#j6hDhu7C$wP{gl$W>*5sU_t$6gCShTfTtF>=cmmB)Y z%>3vAQwQ>ifRKWU#-lTB`^B{P#HO3wnQbh6;2IyuM!2nx?U5C4Kls+aGU>Qz>@s0y z?26f48gsTs*zV^A`va;_hY{lGeO&6&Mw_{3YL}n2J)&2+=Sz~gXH zqV#{XnYBmb36uWfeHT}76;Jgt&D?T^t*0`QNkE8$vxNnOotP9EotmVs-(^pF(EmM6 z&FIjp%@banW4Jl-z*<>UMM~3EA8`Ji>~w8*)kfvU2@W0#Dj*CpOF&4(@aBi9cjq0q zPv5?E6Q=CWnK>ukF~9it^N)4atIC>Pt4@X=?gLrgIDr9(A!Y@nyg1X=BUxW5e{=sc z(f!LT-mJQ2Esagx;q8HEtad-}6`SAvH0%1XzHISkv& z8o>4l2tnM;m$WI7Yx6u~!^eLdQ*MF03lWEgOH=V0%bO35^0A-(@t3=A&9Cj7l^dh2 xA)bWa$c3nC=E@67A06JW{soL622WQ%mvv4FO#sszrq}=g literal 444 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gt&o-U3d6}R5rjqYnU;Bg3y z+4x^~w{?MhpaJLeKVh>Hoz@F+W_O%nmiV%XML|VCh=UV^TUeZ!T3Cwbd_7+H$Nt9z z_bc{NVUx;K3brsSHcnswVh0Ze6$g(C54Nzx=%3PTj460^v=68UMb@D6WSjh(?>75R z*JY==gXA1M8o)XQgcMX}sBEgc>++Yy%Dry$(f|L{rkX3xD-F$E{V-SLK?Xa}C`Pbx zAT0|H-$*&!R3FqfvFrE!gNyex`%X{Y#3=I?;s}_lOKucwdbo}|w)T~;826ebC)qGu zdoblN)Aoh0|NHcu5y-JPnTW*hWl_4Ao3J#^d!$9I58n+u4RbsmT;gkCabf~tuv*8-r#b2p${E##I5<%xV1_8D2mqx(xP=9xM3E7! z*TJI!Xpcec8lT`Dv2I`8#UEN1DIr@A(rI&b`q{Q$;^h?qTB(|TDg~MBK)ne43r?0k zyS?sxqJVPz_V>2;O?#`4oQRddaEs$)XLIRC2d?+-pY3-0dFJidjv`JKbGDZ*Ow-Fi zwm{qna^I|;yEzsg`Pk3zDnHaW_ipRlw=Frph2{W*1K|sh?|qNunYE#^ddgSE@MeIT+X&Y1;7CTgbHpOHJFR?|T$uXHP;C&iDBJU5$DcmS z#gG55-Tf?d^~N(_ImNo~t?vZ78|*rWDGnY1Dx02uKVlPgGyT)A$MXd1h>=iHaqx$gA2*W iO-hU77c*cAm)d1TYWzsGTo(e27zR&QKbLh*2~7ZP#gPU8 diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-5.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-5.png index 520e7f4c2a0d6a902c6c65dc0ded8c277ac16d4a..2fe92561faad22487a4547423d8140009c75e477 100644 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJzX3_DsH{K8<^K@AmHGb zfAsJAH}?{BHx^!-F!O%bGRAouTC$JNC}uFN_haPXY+-R?0%66*2@Hw{<--3xmJIyy z^yi#Ti%%_Ye$7^%>lkEx{^#>&*S}XD|8_V0vM?vlTL%vX6%ZB>;@}hzs)_HpB>nyO z`x(l%WpY;U8=3QNW~MPq++qY9>fq5Z0i^baV))w+zw;;V{&rx+8r2KWI=ka5lu(s8 zczodOHkJM{rS`XP&R)w+`Cks0S}rQt9KD$l-AgKZM;9h%YlyR8UYo6RJ*VRS8MbKa0_~}pXW0z4FoXTt2zHi($At&K_O9QP{mw~d z=X&@1dJ^YnI|kiceF)Q^Ih$fOFLb~E`$w<(uID~-R_zIP?_ObX+m?tmH$Uu?WZU^? z|F_R~&BKnnoqpbT<7@5tJzT%-u49_J#iHaa+XojWMbywp==sR6`6Te>^m_XQVC*t@ My85}Sb4q9e0A_{GQ2+n{ literal 515 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}GtSJY5_^DsH{K8`!tlK)}KA zd&lqiozGb|F11*DEa2`@|M>TR zt7X}0M(-?STA$AeeZ1$Fo8@1(!h7k7U%`SrU4 z#bxeH!PX#OL44-K)WRaWZhP&+8FqX9yiM!OF7#VJFTqs98@GMa!h*MlZ12CmxaH7; zBcdvwd42ZuGWom(xd`GakeOQ|_Vg}#zempef-krHr+Lmlca(?jP;Nx`qta?y>9}uY zZAatZy32gO`BQ&K@tx{_T&>5qrZdmD1qm{^R|~G&N*|C#js{R@G#LJ4G)|7Zc5lAO R8erryc)I$ztaD0e0sv=a#?=4- diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-6.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-6.png index 421f4bf1394002cd8567fc23646a78a00f56057b..831eb2ee18720edff3eabfdb8d6df93a659e72b3 100644 GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gr!JzX3_DsH{K>zQ}hK)}_p zbiwcVnUU$vOnLPIA*;5vvGei^Da=}v-NXIj=SFS^4+RwgArR)^Y+>QxG0s|0(lsR|^6utlb>gE3D$^HwT|KH<(j_u;i%mo|w_Wq68 zIMr*O)$3!=Z@=zh$$JY?4RMZuP)m-?%?p*=_NGs9D5_4@)-m~ZEbGs~PItx9o|uUT z*2+Sh+yb(U(Wz;|u4B*7FTcOt>rm3PrkCIP-0e=U_bpDIQ~Bljdg+@C=!WNPkhpmv z(sY4T@o)RDoxhifswS;=(%JT{G`sjoUz4`bj;+k7UJ#1uP1;kiw^QA&(7y0&$2tAF zXJR{*U_L~+dG_XqCrSm*&CY0ezclF_TR}EE*nOY?0fk#a`_VbPb4693<*z+9d-7_P zn7Q(Ef8BaHPiE@vJL-MDk!#NeMxWCTS3%dOkYbq5`~V|lH(b;|lNbK5yY0nNU>acX MboFyt=akR{0Hf68%>V!Z literal 528 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|^EjA*WbuR9n}Gt?JzX3_DsH{K>zH@gfQRAW zh7bSupZVutvw`Jl$+IYvzPUOo2fAct$}f0%T$Mqwae{+K0}v~y2nZ>t%=jo$-+wIo z`OntRKh_(5T^Fa(ciP|R-_55te#I>n>Z&d|Y;|D!;|*tmIbQLa~=-k&(hi3hImLA(xj0fXYf l30dda4s-xR12rh0iwjL>r}D|czf`Yt%UddasrxgE{2@hKZ7TnqlnU@Wi4$idmd;=}~Pij5N(6djMP{=ef| zQE%V%^S&SX>voG>z5n)a`RNW8xwj4;3MwEhAjH8bAoOQn_`3_)my-mZhT7ThGFK~K zBgkoUt8s#ZM*|RpR4J%%aGuLKvDbB9@w-E{+S+Sw_^GC^V>Q~D-0Sh+A|I-?9wlen z-i3ac`#Z)bP5sF^^U{>+i4qfG27#<>Vd3E1R&BgF@p_+-@z$3;McOh;{w>yit|zne zN2?&%7mAEvUx1vT@Z{{7wv+q#f2X;wxga_BXVBh%w-0f-&s&eFQe}s<>wdWdIZGR3 zr)PgUTzYrn@4qfZ-`ha`ff)!3r+qBxW>2o=yy&laCB^oA@9F>5pZSZhdcAYq%?F=~ zrk$KK13E9ocB3r&fexUbz(Eg_IdDP!)3VJfXCtD zt{?xx%gS5xQo00+CVk)5I;G-Qr{W>wYgLS%`Q8jpOp1*Y7=YNpLqWyCBOp1?PJY^p z66HC*k^h?Pv$r1F$|as%snobumV>i}1%!b*7@eBfHov>H_@zb1+25IRI{$KWp2;0D zl=oNJu$39ryd?#j6hDhu7C$wP{gl$W>*5sU_t$6gCShTfTtF>=cmmB)Y z%>3vAQwQ>ifRKWU#-lTB`^B{P#HO3wnQbh6;2IyuM!2nx?U5C4Kls+aGU>Qz>@s0y z?26f48gsTs*zV^A`va;_hY{lGeO&6&Mw_{3YL}n2J)&2+=^*>?+ahSWwSJBZ)VRK#yOuiu_&kr2yt+Na0`nQQwxh}-&Oe|o&599 z`lPg6eeRZI#hl{Kw7m?Z45GA!g@dz8W?DkN;e36`@UzpOJX-bEHBy{-IHS{AKs4L2Pq)i{Ac(Q)F@6lXtqhx6y3-=8SVXB88A{OShf#t9A{3MwEB zagvB-X_~X!@&9}J=fr=O-M_5jM(Ex5IXqZQqY#lrC?a`iC6dV@%bNWqZ z_4Q)4b7usuoRzoO%y{P(BiJ<#9t~i7D+DI<>Xz81FM9mPG3D0818Zdw{sjh2)AJ6) zTE96LKTX^%ZvEl*dE3=>Wo0S_+3XdCvB*GPo$r1jjrzX`@rhFKVtQowe$ZFJ6>%t$ypXEG)*ntWG;TY5$&n zo%{O*e%tCLoK=_ElUsJM59AaSS08Q6UmQ_fc5t@NyqJT3!!BW}IFZWv@A11Wbw{83 zd^sb~Gu!^d8McCKcDTPl20jRVrTo?PzWKps?mMSucFO7(fc=l~7tjYy;x|h+9kgIm z-uLcnx!xO(2iN#O{sQ|A{P53TzvEr9XF;OXk;vd$@?2>>Qy BrN00G diff --git a/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-9.png b/docs/games/Random_butterflies/img/Random_butterflies-level-Vector-9.png index 63cb3d3e7a60a9e91db7cf8d56ee945cfcacd6ea..053dfb1367b224b9758ddb185ba01af67235cd8d 100644 GIT binary patch literal 505 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|D>#^d(xr%*+&4a_b2@k^s0avwFb8J~3kPSF&DZ0S7fT*Q zTy=3>x1(_Dr{kK2zkgjz4>`whbK-%uvJh1*EKW>{j808z=T|cO*KD)>tUiCH!HeI| ztkc!rfDPo}gc#_=)WYIB@B7@K`;*^yzE?bPeXrM}bHh#G1Jq-2+6WFYV2@6hMIN2sqai`*L)|R>c zx8FETaaP#5aYC6&K{h*@!mX2eh3^-9SX-|C=Els}8#Db*Z0k;NhZzKOd`8jR6z3n$ zF24J7p2Hd%z;HVcT5L30%X9;aK8J(-?QD}udUx(eqfX`c)I$ztaD0e0stNR BzPA7X literal 490 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|D>#^d-e-Tb2jo|f%RzosP;u~>kn+WhYdPzOJzMUl{C|E=DKCDiB&W_>h@^rF#5XFD zU(U42`dd`oy~$~;_D%218$XFRu?EVGAon6X>(umBXV+%MeE9=?^_AZG*4#(1TXz6M cIN&+^LKdeN_gAOP2gV_Tr>mdKI;Vst01xJ^;s5{u diff --git a/docs/games/Random_butterflies/img/Random_butterflies-tile-butterfly-Vector.png b/docs/games/Random_butterflies/img/Random_butterflies-tile-butterfly-Vector.png index c0ac2796817cbb2ec5f405e43f3a93bb6196fe93..758055b8f4658f1313667b0074ee7ebc3484227c 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S diff --git a/docs/games/Random_butterflies/img/Random_butterflies-tile-cocoon-Vector.png b/docs/games/Random_butterflies/img/Random_butterflies-tile-cocoon-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..c0ac2796817cbb2ec5f405e43f3a93bb6196fe93 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- diff --git a/docs/games/Random_butterflies/img/Random_butterflies-tile-wall-Vector.png b/docs/games/Random_butterflies/img/Random_butterflies-tile-wall-Vector.png index c23edfdd306daf6343a992c6f6f5f7ee99bfe1f0..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VZ|jOGbR2e{ozmvv4FO#q9J B4~+l- delta 46 zcmebCouHyA;_2cTQgJK!&;R+1{0T`35>t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy diff --git a/docs/games/Random_butterflies/index.rst b/docs/games/Random_butterflies/index.rst index 5e5f22e6f..4b48a64a1 100644 --- a/docs/games/Random_butterflies/index.rst +++ b/docs/games/Random_butterflies/index.rst @@ -1,6 +1,12 @@ +.. _doc_random_butterflies + Random butterflies ================== +.. code-block:: + + Single-Player/GVGAI/random_butterflies.yaml + Description ------------- @@ -133,6 +139,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects @@ -506,7 +515,7 @@ YAML Delay: 3 Randomize: true Dst: - Object: wall + Object: [wall, cocoon] # If the spider moves into a butterfly it dies - Src: diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-0.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..be9331bfe0d0276e3b68f14d9a871b52907714d0 GIT binary patch literal 1256 zcmeAS@N?(olHy`uVBq!ia0vp^H-Pv92NRHdyVv$10|U!?PZ!6Kid%2*9?ZQJAanfV zb&cyj2j;TcIKH2!yl*;V0NV?O^Tki@=^bF&d3oK%=EWSEt&>FT_doytytqiaL*VbL zbYaJzo9@3C)_Hp_xIw_NgF~>Tg8;SW=HIn-7cO6(oc_7^*PMCnSDsI~^!niE3ul`5 zUw!`QeQAJacjcQPFK0Uo)6G6j;s2Hj%)D#TzMS1D<)LMdWtY>M3_j)lq&oti^`6Eq zEtV({d4DL(;-5{QegElg6?y-e`W6Kj1I2Sil-n%MJ}lYuMk6j_U&i7L!Pj#S2P~G* zJ961FrcC7j&dP;u7nQ?|hb4dhX$E{>&F=Z#(J8MmFZ2 zPByn$_w(%Sb%N46ta=KTY8+vEW^icgB7x{xhei7r3EZA>_-g;6{>kU^s^;$YZ2up> z>AB+4sr$0Fb?DAr5dOzHw>zRld?PVoi3n~ESLZXDsprd`Kbbv!d&}vJX7Am?H+@k# zOAqD!x!7ypwe`gnpBcfQP1-MS49rR8TYfkR7$Th;JC+w`h}@n9^i+l*=LTRfT+rBN z+I|!mGMm1++L*VyZCq=qW85V+F;xH2Q%jSs+_Q;|fpcD0hqX=llvsTB?21DlicZ9@ z-x-;#*tO=U*!7=DtOOGP)X@qqEgViR9mG=~w4P7dED&jOkh^})@x9CE?%!4+b9~CB zWmhz!TrTbZp8rp#^+dc+aYdDa%d~lil@@EboI96zGEhf6$#3_ZvVSvY&pG_l{o}fb z*5vQGZ}w$^d}h|=ceWR6QuJ$?eRm5#|9Bxak?YxoZqaFT4_mGiY0co% zRXTg_;aT^K$2}iQwoSS8OzpA-&+mQPJ9KKB_T7!Vr_^@usPv8Zx=ODVNejI>(esPu zXZ@PbwB0gyv%uldAG=@Im)Gj*<`sXxP|ja_Lu~FW!AFaLArNlZb^DI(9=mxPfGI-o z(WT;uqZ;$hBt8N9mSba+WlV+0{j-TTz`Q=om>iMwXA;kVd9y5I5=8XRB_0Uu`DCko zKJx|t&snoYs~q$XKb&bP0l+XkKewIDL literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-1.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..23b60e96e08196f8175c8ce42cf8b79fa35c7cbe GIT binary patch literal 2945 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

KPlMHfznXP*!FVv&m=iP~ zYR!nE0YiJZ#t5^rW1 z1mzM98{>DpTb32nyBd_w*(8Z*cQf_71-3dtTl#Pq6F6L3tLMjpIvA=LMO|1?a+;B@ ziv%wWYO@AGVQdHoCJy)dm_8m2Kji_E7|o3#$d|)PCtXi73Uf~Vsp`N$$)0gDX@n)m z3hwmDcJujsWMf^XpAtn3#?oel8lUlL2*%1nh&bQUs5)S=ggQ?@LQ!8>qOv1^hvZ>e zk2%ZVV))0@|9Cc3i$7WIvhRWn>x>eWIlY=na8K7v#-l&cgFjS&zbF*{Qy6zgJzbY> zC~2Nn*d#9dO&Hg>^5ZcCx``tC1ET^GAQb+duP*e+!mwqh8%2 z{u;M7CwWF%@uj`qzIN^!zNv?OO~qII`DdgXo~`;hzA$;5@3(Al@8n6InnoeDTpjGd zt#P^0MwAkw*nvXF-?R5q0{-nX=uIh_KyIS2?KlsqLwVN)qsBt`|=&)SA>a zSz<9WE*9l7UR>x*feB~&BRy&=>jy4jJ6qNitsHRVFK1=1;>fqGAj}yITs-r&ujgjq zcn`(~AE+B~Sn;}B4!0c+xdN&rDnTQXO(O1Br;pD=tz1!Y+;~@gtEh|bar?cQshnZQ z&8&Jops^wKamu&~7Ka;oMfwd!SgS2c!Nr44WTBeXD4GBhs1z86 z@%|5kk+q7hPrX&Sl@6(V^qJTb=1gYp2TLgxt$Q+)S5t}jSt&9??ckIG#Fr6+5Rf3e zJ(`TZizf@;#j2~1Yrn+iI9kCKbAK2@)by6E<4l2wEabeD@NW6}j1E#uPhKf4;*kBh zK3`0?rj}7IC*5gw%a=`3cxmn%>#UVDY|poY>lUC{J=NCoFK6=!<&vgX)jFd_HAZj8 m2>59Dxf#tHzR;BxzsIf@h!kh*v)Rb)5iDo8FU?)d;{6KdL_==? literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-3.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Block2D-3.png new file mode 100644 index 0000000000000000000000000000000000000000..8b5dbd30c1c5027989da1f59e6e8e9a6725e86b5 GIT binary patch literal 6113 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-0.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..458f4e0783d15b110e93879262ce292ea5b27c5d GIT binary patch literal 5380 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-1.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e8292f96cacca8cabf2eb37a6ed94e54abaa38a8 GIT binary patch literal 12604 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-2.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..44c3393d3a9c7094952919c7e88b8f1b54d77b5c GIT binary patch literal 15770 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-3.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Sprite2D-3.png new file mode 100644 index 0000000000000000000000000000000000000000..73d55deda61db384f0142c068e28e547f24ad826 GIT binary patch literal 38142 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-2.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-2.png new file mode 100644 index 0000000000000000000000000000000000000000..a615e574f48568dc2987e8613dc3d5afae424e33 GIT binary patch literal 664 zcmeAS@N?(olHy`uVBq!ia0vp^cR-kf2}n+{h`7eUz!dN4;uunK>+RjMxrY@P8ZHJI z3K^$roOelE)IR4C^R!kC`?~j8sznckJzQ5Td?G*N)OtmSDGFXLoQqr_R7?KHN0m~a zqjrYGYt7hk)ZHjrb7O6@hM;PPP+*gWueQo#m*lx-DNobrDOT zk{6gdZO)yJ9cG>B@ zd+}t`UHx<1`-)$gNi;t*-@A6ojeQ{d!A60cY~B)N|InkYgKyJRq1)zQ_kpw`+X&=A zd}bzO{>S(CU8|}awp!Xq&IQ>G*9r6>x&g>R0}3IySNk_T{(sT4ZsJt9eefVeQMah2 zzNFAyeY#}4RcjmnbJp~g5Btt7G5)bFDCsvFEYLvq-YF!P5?^T|E#ddZ9N+<#S z`cS3pPhPs&YH5?^Ft42Z$X;!k9I(mpfESvVf`2n>{M9`DG4X;PFvT%=y85}Sb4q9e E0Ml>&`v3p{ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-3.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-level-Vector-3.png new file mode 100644 index 0000000000000000000000000000000000000000..f8d23fb2db1125aaad1e7ad1b650e0b323906bd9 GIT binary patch literal 1410 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKNXE>OEA|(?u#Tgh_Jw06~)y?rtFumXd_ zK>42y(rC%mf*3}*T&JS{`xC#ym0c2x!zZifvF2blzPfCQM$!X4q9 z)3dyK*;Mb+pu{6fZSv0A*n7yF%^4^27We*M+StK&XaWb-SBggTe~DWM4f%-9)~ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-fixed_wall-Sprite2D.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-fixed_wall-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..76421465c2454e7ae2b8389ec6566c853af9a807 GIT binary patch literal 244 zcmV^9v*}1|EX_d+um)1_v8QG z|Gzm&l7NlE0I+mkMILD*4yQ*q5DWlC9$jy(Vp~<#+ucUIDe`E4d?El)rvQLNnc5Lp zDl8t(mwOm^ik+14ERDZ|!oc%a3nN8USyRSGIj-;iZR?v}x}lT5xUDg`~4ajk?fG()4rm%Ml$=)4nYzGMUcWTSF#(iFV^LqcidS8&t|T rSrf}1+x(r+$e)muATjkA Z3xlaMO4?pELri$9%(<5PmYaL#JKfmKVcGRf>huy4`EZiBhb7Xr7G2D%45Qd#73NGs#II2`>sIYK&FHju5lney~jU=Nh-&+J4Rt1wPMfMwn1qB2R{O#2_ zJMIoxuIu}JcfWm!q?BU8j>dS`YoAL5(0aXQe;kx@-3a}&-(#{`t+duR&=Lg*U2JGQ zYdXR)R$}{)<0yp4FBedNXsa`A(B)1Dku~CI%t0xzhGCdRUxKAB&*b6!)iXGY|{M>vm1kwmR!#g9?%+F{9Y_{_(;l0gD4-e|`S4Nx+hT*iWsN z67tnjMyLCdEA~=#Njuyu$QPaFVl&EinivZdGP(*L`Ee9{z#@N_OA5wbgv+wWMb(bR zWKD-4F=ArpVvqJoTz;^0v7zDxjErk?~nGN)YDj)QfLDhXRjZx zB>#T1!$ArxK)V4vZZ>00000NkvXXu0mjf@lwjk literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-tagger-Vector.png b/docs/games/Robot_Tag_12v12/img/Robot_Tag_12v12-tile-tagger-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..dc863080f5357e6d3b44a51919b3c31da78de881 GIT binary patch literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1|CxWRf!)B+pvNnj ZiDCB>#>$-Px#d8422WQ%mvv4FO#oaU6Ey$; literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_12v12/index.rst b/docs/games/Robot_Tag_12v12/index.rst new file mode 100644 index 000000000..7a91fd446 --- /dev/null +++ b/docs/games/Robot_Tag_12v12/index.rst @@ -0,0 +1,445 @@ +.. _doc_robot_tag_12v12 + +Robot Tag 12v12 +=============== + +.. code-block:: + + Multi-Agent/robot_tag_8.yaml + +Description +------------- + +Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + +Levels +--------- + +.. list-table:: Levels + :header-rows: 1 + + * - + - Vector + - Sprite2D + - Block2D + * - .. list-table:: + + * - Level ID + - 0 + * - Size + - 9x10 + - .. thumbnail:: img/Robot_Tag_12v12-level-Vector-0.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Sprite2D-0.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Block2D-0.png + * - .. list-table:: + + * - Level ID + - 1 + * - Size + - 22x22 + - .. thumbnail:: img/Robot_Tag_12v12-level-Vector-1.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Sprite2D-1.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Block2D-1.png + * - .. list-table:: + + * - Level ID + - 2 + * - Size + - 22x22 + - .. thumbnail:: img/Robot_Tag_12v12-level-Vector-2.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Sprite2D-2.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Block2D-2.png + * - .. list-table:: + + * - Level ID + - 3 + * - Size + - 40x46 + - .. thumbnail:: img/Robot_Tag_12v12-level-Vector-3.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Sprite2D-3.png + - .. thumbnail:: img/Robot_Tag_12v12-level-Block2D-3.png + +Code Example +------------ + +The most basic way to create a Griddly Gym Environment. Defaults to level 0 and SPRITE_2D rendering. + +.. code-block:: python + + + import gym + import griddly + + if __name__ == '__main__': + + env = gym.make('GDY-Robot-Tag-12v12-v0') + env.reset() + + # Replace with your own control algorithm! + for s in range(1000): + obs, reward, done, info = env.step(env.action_space.sample()) + for p in range(env.player_count): + env.render(observer=p) # Renders the environment from the perspective of a single player + + env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() + + +Objects +------- + +.. list-table:: Tiles + :header-rows: 2 + + * - Name -> + - tagger + - moveable_wall + - fixed_wall + * - Map Char -> + - `f` + - `m` + - `W` + * - Vector + - .. image:: img/Robot_Tag_12v12-tile-tagger-Vector.png + - .. image:: img/Robot_Tag_12v12-tile-moveable_wall-Vector.png + - .. image:: img/Robot_Tag_12v12-tile-fixed_wall-Vector.png + * - Sprite2D + - .. image:: img/Robot_Tag_12v12-tile-tagger-Sprite2D.png + - .. image:: img/Robot_Tag_12v12-tile-moveable_wall-Sprite2D.png + - .. image:: img/Robot_Tag_12v12-tile-fixed_wall-Sprite2D.png + * - Block2D + - .. image:: img/Robot_Tag_12v12-tile-tagger-Block2D.png + - .. image:: img/Robot_Tag_12v12-tile-moveable_wall-Block2D.png + - .. image:: img/Robot_Tag_12v12-tile-fixed_wall-Block2D.png + + +Actions +------- + +move +^^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +initialize_is_tagged +^^^^^^^^^^^^^^^^^^^^ + +:Internal: This action can only be called from other actions, not by the player. + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Initialize Tagged + * - 2 + - Initialize Not Tagged + + +tag +^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +YAML +---- + +.. code-block:: YAML + + Version: "0.1" + Environment: + Name: Robot Tag 12v12 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 8 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W . . f2 . f3 . . W + W . . . . . . . W + W f1 . . . . . f4 W + W . . . . . . . W + W . . . . . . . W + W f8 . . . . . f5 W + W . . . . . . . W + W . . f7 . f6 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f3 . . W + W . f1 . . . . . . . . . . . . . . . . f4 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f8 . . . . . . . . . . . . . . . . f5 . W + W . . f7 . . . . . . . . . . . . . . f6 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . f2 . m . . . . . . . . . . m . f3 . . W + W . f1 . . m . . . . . . . . . . m . . f4 . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . f8 . . m . . . . . . . . . . m . . f5 . W + W . . f7 . m . . . . . . . . . . m . f6 . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f2 . . . . m . . m . . . . . . . . . . m . . m . . . . f3 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . f1 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f4 . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . f8 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f5 . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f7 . . . . m . . m . . . . . . . . . . m . . m . . . . f6 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + + Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [tagger, moveable_wall] + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + + Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square + + diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-0.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..6c2a91165cf3facf52914b1fd2c94dff1c0392a9 GIT binary patch literal 993 zcmeAS@N?(olHy`uVBq!ia0vp^H-Pv92NRHdyVv$10|WC4PZ!6Kid%2*T+DssAkubm zc8As^qdRTC3nZtVmUy**_m;=48|?4BGRjYGGBEyO_1q>hVFFWp-6W55r)|zNp8xhr z)uBVdrG>-Eg#h*KO~C8UX{kHQ!aHXudAa z=h@nVj}2U0Ea&gNtQnTaG9Tqr!Sbf7p%QpsHXT?9R|D7(f`Ap*Wz#P$}l~q#y z^ABHJe4|Woe}o-K`R|^j$pYS+Cs#g_F_S&o*8SuBS>vwAXB!S{T;tzZQpw>z^KjYX z8(D(+G1egE(fvtJ1-!qrU#jGj?oFEP`e*%Iu=1B0*LXJCe0^9Fw{_Rzh~H!2d)u*bo(oJSkI%l=LMy8(Pkn^Q$xWf9n}cq z*!W0B>S#=HM4`xP-K43uVDVoXVLRr0NuHp$5?E7> zfRYo7qLK?46h6;A;VSGga{Jh|JkV>(rtJd9HRd1wVc8RPCg@>-O#9;-YhC5f*39mI zR?2rh85C*P59ck;5d10EU18boW_VrZn~ZDs3G;{}_RlY9JeviUy!l%xZnpE{8QH+} zv&c2&diRg7;267+D8k=6@4Lw53cc-fmNoj$SoT?WqHpjf0oQ38kJkR%#01VMld5Ju jzqz`>l}N89)owcc!8GP+|yxcRK?@=cYQf~LOS>3#-j;fV;htg z@8%C!)vISzxrzC8%rcW}*H!PUNm$PopI^S`hk@OKSmPg8uJ2!xHGMjh!q=Zt$4*UG z+B1o<;6uWdclsO(631tXyRjXZT=Hen_v6{#pJSq9xf+=Ddv2X|Wq47ND8bGk290lw z*ayZiQY6Ee)KzvMpUwS%lx2GR2IZw9?G3Hagh#GrwGuLn*;9gKm?9+5k}}vt+=&;K z;;E=?1ADV_hvxG;Ppp7Gds%q=5Cd{ffTebfEOKzTD_g)1440Kt9tqgIOGreEC6Wpp zs>GSV$p;(}%Fo{jlq~@|X#=dFA~SkGeoN>L>T+h_W=1Rez$VhFu)|iuHq4a>mJJr= zVaQwh=-6~eo_XvH+_MAypHI1D!Y(T*#&)3gv(lRr0z3__>pnkv&3lj`;o_tYGb0w} z2Yr=-l_@NIZfjSD*|Qx;{;c%iM2A#Ql>hUOC32?BJIdpL#n(So)(u&k}DFW{aQISCwFJpDphDPEUfN@!8Qv z_qGIvEqB=2$|?VIkmtu*y_dZB|1C}{A=%pWhXMU+>iv88?dqmmKNz( zXgGw&6`k{A1f?BV`h!)N#3dXVMu=lKtc@2wee+LE#*}%$76OB(tDnm{r-UW|vM36@ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-2.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3cbe86756d1aba3636fdb8104d8822f1f77798 GIT binary patch literal 2201 zcmeAS@N?(olHy`uVBq!ia0y~yU=jdf4kn;TeA0n?3=A9zo-U3d6}R5reVBX8LFU-Q z?5DaC%>4&tK1wW?ejL%<^RP#-w6|_Sm(o^Kw)ohh|IdF;@#vf?(>VWL!;$TCikTXs z_gOPBurMh<(jLeMEg+X1HV+_t0Pk!C=_=Q2?{(}8GywBY{ z#E@X$#MqF)pdi7(!Nve%qVm8Ja5-u#I}mJrwPaNjg$ZPZw#)CdPu}dpc~49$&xKVlf1dqe)6RGyC(dO5lmvz^X8N;gb|)~f%o3OF=Q+rbv6J6QPO{lv zu;KOVS$r>Ze;ZZI-BsJ$+sgRCd-e3IQ4$P_Wo%-IXo5urM*IOKU~))NNd;veLfXQ_ zXFb1}$u_09j3eGdbLr{(ryrl+boyyhBEtrGlq5;hQo_$^?6hW@!;_FjA;}*u&;75PWXu$x<&(zRPd>S{dQ0kir;A1B^M$ zl^rOSka6@`D$@R-jGdd3(g`CWkWy604s9OEU!t?scCZeP(CiIJQeQF$GB7hY0iMaws!L zlz4re^2jcrC0e5Xj<6fshGk;*eaq#K2j56*;XHQgYeEXpoYUUG@-OdP_v_<{?ruE^ zceYCR@H9N&e*CP>z^GyNXQfXkI0Smw#hNEF3X~L>N}tZUv_s45}Juu$w6bN4~8!41lb@0$l?ZQ{@Jz5FDP*>LZ&mtVrJ z*UU^{nJX@TzoMeDu$}R20f)TR-{~QTm&@BsX1q{ruC+iHRWZT=;t^V-r8n zS^TzkFKvA_mpV^QHSmp7Wxeq8P{y}SQ4G6cCFYm0F`$*A4>{m9E4=nZPliAVxExYJ zM~<=v{kPHQZfY_lB%)Qkz+?$anXqC6m_jiVX`3?CE#JNh$7)Gj++8o?3T$#Pc)I$z JtaD0e0st}sPig=F literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-3.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Block2D-3.png new file mode 100644 index 0000000000000000000000000000000000000000..d3f4b76f58e2da7c81e44465ae6b809ec08a2551 GIT binary patch literal 5301 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz!Jd01Qc0Yx}K4NL8Qjh#WAGf*4sOGb8j0k zv<7awASQWO>Tq#LvVcr;++j&)Ipr5pwFwuk{!L7s-kbPoj=DMrOUtH4e(|RLM0!9W02Wy~B2p%&-LjxNF10yd``7J>PBp!<9 zKA>^vMlJAlUdz37%A99SmGf4|q|3OszpVSY|G@J@&+q=e@VCmLXXB|-9k{i~76FaM z?t&pEvCGi1<;_24hCBJQ4{v)s;g!jPVvgNskLP_}xR8ssU*U7kpKleEVo138 z^G{5Pg;O**_Tr0j>-2hnp?W=L_WbU9ZW-L52!3IF`g3t&3mb#QcAycly%(I-|34RT zvfH!vr!fOV-(LU3+4rygnc0xH0qC%bH4A?pHXf!C(-3ZSW!D9p^M1k=T@s6(D-<g_gU2-FA zU;wT2f*3KNNFw7(z-y<+7FK6jKigQd{O}L*;$w8^zblfwrbu=ev^XxT%RO;>0xPi9 ze#h^5tGu;gg**d8LAdktvwAHCvR1MSb%D*ZYtmb_&$9(tOwCz`cIAPT$2ZxI+dEb_fP+H+!}Y zGLA6ac7w)u1E+Ga3+YfaU9%hF=FI0-HeoohykS>;;Jd^YxvHK2FPB6JAHV!$F%v_{x>I#~cz@b% zi41dgf6rRT!?5Aj;pn1!Zd=k){o-3|Ux wfB!v;8p0z{2QJrk8(Oh4FhrcB`S?XVN9UhxEeV62zyUu7Pgg&ebxsLQ08_@2R{#J2 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-0.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..aa190b14cd6102db8c84ac5dd56cc847ccee451b GIT binary patch literal 4058 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 I*Sj12KQJ=nnE(I) literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-1.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ec3a96a359675434481833952d7178dd051f8921 GIT binary patch literal 10499 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 +3J4zm)NcZgT9S{R#X$c%5v{FhYs)Yu zG*PPMxgsraj4SR$lnt-W(DRh2Pt?!@?0RfYE$eBkj~aqjlGxTMTEIP4l*>fg@i!ef zkna7T0%qDz^`AMadLod~szi1EUwVb{Z}Kio4zbtoyXh|)0gFTk;0)Gen8C{bvjDTM zZXEhEbP9J1q5x;`LBsEb88nzBNd87^$W#V zktqI|^xG}vG3g!Sot>RyC(02?t(d;5?a&u3dgIjTXupMNtG+WvuQo%;n*zcn@ollr z*Ggm6}<-|IVA z6;ysEImbHo`*b-)#V4pbh(51cz0?ZwM2D&$kp1#oNBWoy^_y@v$R1r!j)`#(rsvnO z8*5yEYTySv8wuQ^@gTW~+;!+Bx9BD)8QBKgyYFI&`+l&~NG**364r@*F}#V0j>Y`3 zmZ@-3=mU@bybvN;rO>d8c}};e{-((}|AKOZ)D1QJdh%RgQ`r}Dx<7!&aB_fO8COEk d;=WZ;ebTi!*N4Tc;Bh0Qr)_XD`?&2N{|gkm5Ptvw literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-2.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b90ce2e7760444f431b1cf6e66373bca7c7e830e GIT binary patch literal 13013 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-3.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Sprite2D-3.png new file mode 100644 index 0000000000000000000000000000000000000000..be96b2c87a8db625821a7ab8a5e5aa5951296eb1 GIT binary patch literal 36166 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-0.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-0.png new file mode 100644 index 0000000000000000000000000000000000000000..778cb9407b9383b58e37b15c33ff7d194fac77a5 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^Q9zu+!2~2bcWvkZQinZV978H@y}h$hut9;xA@H#! zOY7&>T4WabeCFaTp8kyPoWGo6Eytp_?e!8;ly|z7<=gD5X zT2i3{6a>lkJ?#3ke#?>jdNH$Keu#jGfptK&wm=Pln*?DTf*Fh|{;=Xrs&UA^)R*0} q7y3YK1}Zq-(5m}u8Qh@7)#8`Lc-cQq4{-!~hr!d;&t;ucLK6Um?Oz)J literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-1.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-1.png new file mode 100644 index 0000000000000000000000000000000000000000..150fb7dd49962c86a6cd6aaf60630c3fda5d80a3 GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0vp^cR-kf2}n+{h`7eUz@+Kv;uunK>+M~`zGedfhl>`U z6pI4=4mv&*ThcAkwUC3a;!j+6Sk#H@I~>eXZ^{*D`}Z>hHfac|b_hYKeOG^cT3z8X zC;zUGm(G0!FBi^5E>je|1XLHX1S%mYuzJrW|Gr!=t|IdP zw(^Tz%({GCB0>hmHlU@BQ#yoPJhe)qs(;q?7@x{e{5K2e!Kc#5wjdh`iL<$u7NxrPycyL%v4`Z(KL@3?gQbse T=+_eirX2=PS3j3^P6+RixyoVGd8UhVv z4?VSN3ohy53*UBNvs{Lh^MmE@V|}Hf_cLQjvB?JZ5IP=7%54WPfUf$krzsFBG zW7&~UykNypJwGlMxgVeBTQb+}f=}mDX_rn7$0;2`Um3Y|8G$0tzwq5ElB*=no)qR literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-3.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-level-Vector-3.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7f747b4400013aadd83930473b5987d9fef749 GIT binary patch literal 1386 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKNXE>OEA|(?u#Tgh_4Lw~PLn>~)z4LbEVFihX zi?5F=#^@!NboESpI7@erw8V}p`|9R=HV+6o%FcTG%Q}08;2)+e4GD}KVhRxqO{@wL zU^bAbKJoUr>d`&dF7BzXUnAv-O3#nuy_s9D`$G+Xb;O@k5kV!ztVFLpTH;@3a zfdmGdgAkU%6!uGXm9x4aaA{n;w1CyEwZhJ$c=`Z|n8n zKz%}-RXE*o&%adx9fq_LZHgE07qm|M#_3R_uWX2j0*_mK0DZ z|MO<^&o8MT5~Cbf--cR)RgTe~DWM4f%-9)~ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-fixed_wall-Sprite2D.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-fixed_wall-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..76421465c2454e7ae2b8389ec6566c853af9a807 GIT binary patch literal 244 zcmV^9v*}1*ZiB$$e)muATjkA Z3qv?R+um)1_v8QG z|Gzm&l7NlE0I+mkMILD*4yQ*q5DWlC9$jy(Vp~<#+ucUIDe`E4d?El)rvQLNnc5Lp zDl8t(mwOm^ik+14ERDZ|!oc%a3nN8USyRSGIj-;iZR?v}x}lT5xUDg`~4ajk?fG()4rm%Ml$=)4nYzGMUcWTSF#(iFV^LqcidS8&t|T rSrf}1_xwEHz;0k@(BqZN Z#IW9yF@M+O;!i+%22WQ%mvv4FO#oH)6CMBn literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-tagger-Block2D.png b/docs/games/Robot_Tag_4v4/img/Robot_Tag_4v4-tile-tagger-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..3fe75e180fad1754366b239ff9b055829590d275 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9;hrvzAr-fh6C_v<3!X_ynBgfJ z+|xT>O4?pELri$9%(<5PmYaL#JKfmKVcGRf>huy4`EZiBhb7Xr7G28aaU4hDduxf1S|b9`G!4UF7FFDNzirz~2=p)Y*Xi4m)$O8(xmwC9>DbQQcLEX)8SC$G7TW~nMv#zBVC9r zKMg*YK)E;>>iNk&%KJ#anSb6g6!F~mAHd#P?UK16wX^dDNOf4F0xWPIEp^xN}1|G9s*f!)B+pvNnj ZiQz>C + - tagger + - moveable_wall + - fixed_wall + * - Map Char -> + - `f` + - `m` + - `W` + * - Vector + - .. image:: img/Robot_Tag_4v4-tile-tagger-Vector.png + - .. image:: img/Robot_Tag_4v4-tile-moveable_wall-Vector.png + - .. image:: img/Robot_Tag_4v4-tile-fixed_wall-Vector.png + * - Sprite2D + - .. image:: img/Robot_Tag_4v4-tile-tagger-Sprite2D.png + - .. image:: img/Robot_Tag_4v4-tile-moveable_wall-Sprite2D.png + - .. image:: img/Robot_Tag_4v4-tile-fixed_wall-Sprite2D.png + * - Block2D + - .. image:: img/Robot_Tag_4v4-tile-tagger-Block2D.png + - .. image:: img/Robot_Tag_4v4-tile-moveable_wall-Block2D.png + - .. image:: img/Robot_Tag_4v4-tile-fixed_wall-Block2D.png + + +Actions +------- + +move +^^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +initialize_is_tagged +^^^^^^^^^^^^^^^^^^^^ + +:Internal: This action can only be called from other actions, not by the player. + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Initialize Tagged + * - 2 + - Initialize Not Tagged + + +tag +^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +YAML +---- + +.. code-block:: YAML + + Version: "0.1" + Environment: + Name: Robot Tag 4v4 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 4 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f1 . . . . . . . . . . . . . . . f2 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . f3 . . . . . . . . . . . . . . f4 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f1 . m . . . . . . . . . . m . f2 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f3 . m . . . . . . . . . . m . f4 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f1 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f2 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f4 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + + Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + + Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square + + diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-0.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..41745cc8cf4c469e538b504ce35b42e8a2c916b0 GIT binary patch literal 1895 zcmbW2Yg7|Q6vqQnN6l!qV`6lszILWovDi=ZVyutp4#iYOMkdTLug^z`vz{`bzz&fRn7{?31R z0e)*ujjfGwIGm~PI%E?T?_y)jNFUSDE2{@^xc41>5zjzY@2KczrfQ|R$APbXRmtB4 zSfi114Jq_Xg9+AV#}=heh3^zZUL!PiU(CEewH%yiQ!8pV45(UlEbs`!jHvH{;1C1E zzytZW30XbzO2?eOc4R%PruRl`>*bWqlnxS4{i*b-`*EUR$aAr9DU{9jJg-e6U3|hn ztQnv2uNh)m01Bq%U}pZU&yYP``MC#Z3By@&#ucd+fX&cJsfPWN?%cRSA4<-aUV|M^ zeRCO}*ec62PFt%xfApYF)yF`gi_#VK&$|-@Hae*Kr{D9@#m%@Kk8px2%0=72Ej<;5 z@I;V|{gjO)7NuUHR+#`{kMh02utye;T|#AwSO-w9h>cl;#-fbRmS3d_6Ux)7Nt$XTH3Dy^L>H>6%_?(I)`7TaIB zSaNqauyKDBftpo7A<7No?S5_YO}Ss_lQkup={x6cbLz28zjgz#(P{bQWE#|C?%bif zWaEq)agB~}k8p9^ze)bOA>x-npWiAdLwm?)m*#5RycE0={v2;Gghdel^qtf%;agE4 zHEBI0X3lLEl%H%}2$cpMx8v`!rf$z!m9A>>Y!UjW4Uyc@eN%Vnph(TlCWbm0;29fM zn6$9{2C;@&De2)L{yN@5&VKsLiYkEa2V$SEu$(PWZG3O)c!Vdg_qj=562StyeSW$a zEgi(6c&vB&64vFzU(%ZHHIKoi4|AU8ROj(I5eFh(Eawp2)+X*4eR*cd>18Uf7hdA} zfU6l86HX))`}^sBF$=D-D-I1IbK+>JUo%BWRrp&v-t^dnr`l__TEc=W?dFw%%R+9# zad5~S+R`AIu$IYJUGHt6V&(HClN79g`&KRQIm+%0hS9uxJ*glc)D?HRB5$REs-u}5 z6thVES#8~lvZ#4-QBce(bb{I!P%ozaE-N{(lz6U6EMws5j-R>6*& z?T?Lq6?htcp%e8Lb%x=`;~DXB+x(*K$A(80u^o}Qhkg|7WZomEOJ9{Szz$!l z;KmQR?dNA-*mtoDTt@A4?RG9)@+AM!86~*S?^*A3K}7mqdClnAIXo6e400SLC+x|^V{zw z;-XpmUrwA#oiU4>V$Jg((FOAP_R;ReuVZO+XLM(aW*>QL)ob@G^UO+o9e z9Sta4dQPbe1gPC)wNi)pV2)8NSqBWryM!OKx6tR$s4G!8boh;sTrk(!LPo(%Vri>= z6?8~JPXL>TNGs8>B!Z<}SnGhNqU+2KZutd|p&}Je+H&Z$)C}(#`0DKjFWNco5Q(vO zK9J2Mn9JovLnQGNPQhz%ENDxg$?ryIlxt%`)iT?0RL6b5N=r%K?p>vKhHP8brY)uJ z5$>+#3)AfmdF0(ob`xl4UPYWRvK-3bTXGe2-Wdow>5UpaaN($FL&-l(=NJMhD|`pZ zMCN&}`N5-gt2#EzDjFFcZkrIs`~S`3-+2&ZDu$IJ$h4$-eufW%H@Q_3eS25P?NdqX z8@AzHDYKw7^;q@RwIHQf`5@*A1qA`~Puw_r5nVHJf=I>OYOAN2M8e9G#$i{i2WZ;p zA)t1oQVDNDJY>6)2!Onpm`sqz9oo4gCu(j_Q)jHlc#}`H9$>Sls!lJ_C+fsopPTa{ p(S4q)vBx_Yy`l6USI#b+(Ki~76fNKd)3FB>=S%fND!jsv{swa!GMxYb literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-1.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6d711a4110e07da06b72d152664b5e930571f046 GIT binary patch literal 2708 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 Jsy*qMzXI_7R5Snp literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-3.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Block2D-3.png new file mode 100644 index 0000000000000000000000000000000000000000..3affb57896b810533f384496e7c9120da74913c4 GIT binary patch literal 6641 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 K09LMM8}={s3$Zi+ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-1.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e43b91a99cef3d893a5e8d590dfdecaf59114e85 GIT binary patch literal 13629 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-2.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Sprite2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..4e16ca6fe2120c15cc365e826231b79b3c75bb6c GIT binary patch literal 17277 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 literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-0.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-0.png new file mode 100644 index 0000000000000000000000000000000000000000..4defc15fb218362be404b4d7e54dece2b524a9ab GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^Q9zu+!2~2bcWvkZQa3zZ978H@y}jee)u152dZCB! z1+PuC`G>Zx*}^ZJQgrU!KgD8Orx3C9^_?dt`xw~yWGo6E9B5Qla!=pO6Ju>2{(8&J zGQYPUHUI^gL9%w+wPihdXI+IVWM=Q%|JLjCfqR8#Bi}-m6g-H~U->yM^XFYz>x19n zIzi@wjVR#gE1&Zq8)g@?dkrctt*&|g?A4{W)|A0@)aag!|FRXL8{|A2^An|F{{IcW R)Byd+;OXk;vd$@?2>{!GYrp^i literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-1.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-1.png new file mode 100644 index 0000000000000000000000000000000000000000..84997a7a0415392a060e186ef1655e4c5a0e6e3a GIT binary patch literal 616 zcmeAS@N?(olHy`uVBq!ia0vp^cR-kf2}n+{h`7eUz@+c#;uunK>+N0db%zur8Ujsq zCdh6uD?7kfn!_&P93A)EXLhzu%}+^_y>|D1GCEgAGP!Ura+#vw1*Lw7)y4Qv>)=kA zEOgwwLnyFGLr}Ft$fZ-`A^*ce=lia%KKh7%{bjycQ!Ms5P611RDNgoW|B^+lcOq)P zcYAg|mIi8Ygc({eYn!^&Q6{~$m;YgCL$(^|0+v9dnH%oPKIhzDY@`V{0PMJw?BBK* zJN#y1yOJp0v!r<&%+SyeT#$t@6S3ZI}_#vun9OdLVXDL4?HlyVPY^r zwBB8Pr}AV_C_p@m>}t4E7PUyXzI|um|6}gn#mrFWL8qfAy;Q^IToeFE&J6}`|=USvjiS;ye&L8Xs?v+`%7KmKt_tWg2~$T?#mg`M?y!;OXk;vd$@?2>^w(+*$wt literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-2.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-2.png new file mode 100644 index 0000000000000000000000000000000000000000..f6405ed4c7febecbab28c277648a910223a13306 GIT binary patch literal 665 zcmeAS@N?(olHy`uVBq!ia0vp^cR-kf2}n+{h`7eUz?9(W;uunK>+Rjsy@wS<94?;h z*c)BUe#MP_%QLP?rF$46k35`Qye&*gdYv4%wsC*@ujYk+qM2Mc7r9JP@Pbkm;#${} zm;biv3OAQF|LLVRS?IWVhfrXXhM;PPkV~h=!={4A-?2}%C9a`H`0V#4Rkz8002#fYXi9(?s;GsAZzN>NL0}NCqMT^rw__8o}JA%(H*>F!8f<`*Cp}a_qKPK)i(U~Ra5QD z$7(b*E*z)msF>7N<^Rap_T%vDE&EZ_16_2;^S1Qdm_56q?=4M*#3LfiA9^3^y+2p? t{S&LV3(#Yu;8TTKcY`NdusByUPQM}dUe55K7cj{&c)I$ztaD0e0sx`s0*n9v literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-3.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-level-Vector-3.png new file mode 100644 index 0000000000000000000000000000000000000000..87c50c37e142ffc747996e554bc62fb19ecb93b2 GIT binary patch literal 1431 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKNXE>OEA|(?u#Tgh_<2+p)Ln>~)y>l`5u!4xg z#U-EihbJ?iKgO1C>OUcmAyk%UMsZ!Bu=sTC2?6)#|6!VOs9%$Th1+2R!$Bqi9UuW_ zvv5Zo4wJOEvb`QDbGLoY>9X@Sk8}3yS={?)wn1Ung7`-=tN?C<&qf61skly0|YTlANx--+$47D7xMVnAnt zTnltEm>rV8>;2J-Au_y|t!=g+3*Tn``-yfcZqHRm&Tx3J&*(-XV_yr*2-FZjGas*H zQz{oYz|s7T%U8%TgY0EwHbxW@ElIROWGf!1&DN~g5hG*!#|;=Jcs)ykByx&^h3Ee* zGn?A2q1M0zp^*qR8XTb{STkx0o+Mn*{PvPRFq_VZiwN3`Bf%d~$}+!uUU7ca_ec9w z{C}Kjzy3`%dEL7WhnZwJ8If}`n7zvUZuMPF`@OTGcbJ~b$hN-?%|y_m11&a?GbuPu zn{P_hm1*_0oovr6jk*gfeo&Qd=$K}Hckxa2<$m|Mi#>(c#fA;lMJ(m9}XZ+?ytgEdk~h|tdViJS4h&g6%ees2U8xD1}IelF{r5}E)Y CvyI~b literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Block2D.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..23b17524e93d7eca0cd517524637f785cd4be25b GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9N}eu`Ar-fh*Q{TkzJM(uX@QW0 nX~vZWfz58bQb2~nCq{-FI*e;Cu6rX7RL$V&>gTe~DWM4f%-9)~ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Sprite2D.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-fixed_wall-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..76421465c2454e7ae2b8389ec6566c853af9a807 GIT binary patch literal 244 zcmV^9v*}1|Jgr*kv}0RL1OAL a76zvGj9b3Shx`D_GkCiCxvX+um)1_v8QG z|Gzm&l7NlE0I+mkMILD*4yQ*q5DWlC9$jy(Vp~<#+ucUIDe`E4d?El)rvQLNnc5Lp zDl8t(mwOm^ik+14ERDZ|!oc%a3nN8USyRSGIj-;iZR?v}x}lT5xUDg`~4ajk?fG()4rm%Ml$=)4nYzGMUcWTSF#(iFV^LqcidS8&t|T rSrf}1|M@?Wkv}0RL1OAL a7KUVfCYA}0=Qji889ZJ6T-G@yGywoq*b>hG literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Block2D.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..13f54c88ddfb1b9470756a6b1fd68ed24c9c7a13 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9lRRA~qy>OAYMM0qD;ZFOq z6zS^^#8M@{74kphVT`{0= BQ~Ce^ literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Sprite2D.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1c492c6c74f37c11109978164839ed821e8d33 GIT binary patch literal 453 zcmV;$0XqJPP)D%45Qd#73NGs#II2`>sIYK&FHju5lney~jU=Nh-&+J4Rt1wPMfMwn1qB2R{O#2_ zJMIoxuIu}JcfWm!q?BU8j>dS`YoAL5(0aXQe;kx@-3a}&-(#{`t+duR&=Lg*U2JGQ zYdXR)R$}{)<0yp4FBedNXsa`A(B)1Dku~CI%t0xzhGCdRUxKAB&*b6!)iXGY|{M>vm1kwmR!#g9?%+F{9Y_{_(;l0gD4-e|`S4Nx+hT*iWsN z67tnjMyLCdEA~=#Njuyu$QPaFVl&EinivZdGP(*L`Ee9{z#@N_OA5wbgv+wWMb(bR zWKD-4F=ArpVvqJoTz;^0v7zDxjErk?~nGN)YDj)QfLDhXRjZx zB>#T1!$ArxK)V4vZZ>00000NkvXXu0mjf@lwjk literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Vector.png b/docs/games/Robot_Tag_8v8/img/Robot_Tag_8v8-tile-tagger-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..e3112deb3a8502fccc4c821559aa6e7dcca88167 GIT binary patch literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1|B*k?z;0k@(BqZN Z#IP`jG1-33f)hY_22WQ%mvv4FO#nh=5>EgC literal 0 HcmV?d00001 diff --git a/docs/games/Robot_Tag_8v8/index.rst b/docs/games/Robot_Tag_8v8/index.rst new file mode 100644 index 000000000..35216fa42 --- /dev/null +++ b/docs/games/Robot_Tag_8v8/index.rst @@ -0,0 +1,445 @@ +.. _doc_robot_tag_8v8 + +Robot Tag 8v8 +============= + +.. code-block:: + + Multi-Agent/robot_tag_12.yaml + +Description +------------- + +Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + +Levels +--------- + +.. list-table:: Levels + :header-rows: 1 + + * - + - Vector + - Sprite2D + - Block2D + * - .. list-table:: + + * - Level ID + - 0 + * - Size + - 9x10 + - .. thumbnail:: img/Robot_Tag_8v8-level-Vector-0.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Sprite2D-0.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Block2D-0.png + * - .. list-table:: + + * - Level ID + - 1 + * - Size + - 22x22 + - .. thumbnail:: img/Robot_Tag_8v8-level-Vector-1.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Sprite2D-1.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Block2D-1.png + * - .. list-table:: + + * - Level ID + - 2 + * - Size + - 22x22 + - .. thumbnail:: img/Robot_Tag_8v8-level-Vector-2.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Sprite2D-2.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Block2D-2.png + * - .. list-table:: + + * - Level ID + - 3 + * - Size + - 40x46 + - .. thumbnail:: img/Robot_Tag_8v8-level-Vector-3.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Sprite2D-3.png + - .. thumbnail:: img/Robot_Tag_8v8-level-Block2D-3.png + +Code Example +------------ + +The most basic way to create a Griddly Gym Environment. Defaults to level 0 and SPRITE_2D rendering. + +.. code-block:: python + + + import gym + import griddly + + if __name__ == '__main__': + + env = gym.make('GDY-Robot-Tag-8v8-v0') + env.reset() + + # Replace with your own control algorithm! + for s in range(1000): + obs, reward, done, info = env.step(env.action_space.sample()) + for p in range(env.player_count): + env.render(observer=p) # Renders the environment from the perspective of a single player + + env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() + + +Objects +------- + +.. list-table:: Tiles + :header-rows: 2 + + * - Name -> + - tagger + - moveable_wall + - fixed_wall + * - Map Char -> + - `f` + - `m` + - `W` + * - Vector + - .. image:: img/Robot_Tag_8v8-tile-tagger-Vector.png + - .. image:: img/Robot_Tag_8v8-tile-moveable_wall-Vector.png + - .. image:: img/Robot_Tag_8v8-tile-fixed_wall-Vector.png + * - Sprite2D + - .. image:: img/Robot_Tag_8v8-tile-tagger-Sprite2D.png + - .. image:: img/Robot_Tag_8v8-tile-moveable_wall-Sprite2D.png + - .. image:: img/Robot_Tag_8v8-tile-fixed_wall-Sprite2D.png + * - Block2D + - .. image:: img/Robot_Tag_8v8-tile-tagger-Block2D.png + - .. image:: img/Robot_Tag_8v8-tile-moveable_wall-Block2D.png + - .. image:: img/Robot_Tag_8v8-tile-fixed_wall-Block2D.png + + +Actions +------- + +move +^^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +initialize_is_tagged +^^^^^^^^^^^^^^^^^^^^ + +:Internal: This action can only be called from other actions, not by the player. + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Initialize Tagged + * - 2 + - Initialize Not Tagged + + +tag +^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +YAML +---- + +.. code-block:: YAML + + Version: "0.1" + Environment: + Name: Robot Tag 8v8 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 12 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W . . f2 . f12 . . W + W . . . . . . . W + W f1 . f3 . f10 . f11 W + W . . . . . . . W + W . . . . . . . W + W f4 . f5 . f7 . f8 W + W . . . . . . . W + W . . f6 . f9 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f12 . . W + W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W + W . . f6 . . . . . . . . . . . . . . f9 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . f2 . m . . . . . . . . . . m . f12 . . W + W . f1 f3 . m . . . . . . . . . . m . f10 f11 . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . f4 f5 . m . . . . . . . . . . m . f7 f8 . W + W . . f6 . m . . . . . . . . . . m . f9 . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f2 . . . . m . . m . . . . . . . . . . m . . m . . . . f12 m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f10 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . f1 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f11 . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . f4 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f8 . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f5 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f7 . . W + W . . . . m f6 . . . . m . . m . . . . . . . . . . m . . m . . . . f9 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + + Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + + Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square + + diff --git a/docs/games/Sokoban/img/Sokoban-level-Vector-0.png b/docs/games/Sokoban/img/Sokoban-level-Vector-0.png index 4985d5a1c9cf3e475fd6fe026c19d9e76751b0a3..b046160f8bdcb829c5736cc5dc4c935bd39fc2de 100644 GIT binary patch literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLn`QlC6s978H@y}f&u*TF%cAyC;X zX!4>fTWou0Tl60BXL-N7t9$z0lueFX^sH@T-wP%Pu<;yfaFj6MI4mvo>h#QqlCeQ9 zQM_iVi3b#TBw84o4Qgt-)=Br?m>q>8I%Cm%jUugeX=lvuW}Dua(gHGpnJw|ag73Z; zt&2ZRdAc{z6f6KSrOw3l{ap3qoz-()xk1VxqJ4dZHC z1Uu?Oqu*RtbNiEX)K8#aHzsVtb=5(L`fw=(SCpwQi}lfn-4J%gvKpUXO@ GgeCxLyKa>L literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLn`QZGGS978H@y}j$m)odW(aM9ltP(AZ%?1eqY&?eyoGzbyo?Yg? zUvvGkgNs#opfYW{^DkaKC6@OgSm&ro#T2;mgzk42;$l6X?|8K)^MeK_)Q|?p$GI23 z#(tmc^)+V(TjBu)s4*9sx4qc>e)8gnYcfH0H8@Hba4@so&F;>-S^C_77bcK+V8Q>} ti=ylNy_dn91ytIm{U?44vcB4zjJq^NCL8`@JOT7HgQu&X%Q~loCIE@8Z(aZZ diff --git a/docs/games/Sokoban/img/Sokoban-level-Vector-1.png b/docs/games/Sokoban/img/Sokoban-level-Vector-1.png index 2dae033b4e1fdc5cfeaecbc930f72110ce8fc30f..e1b75331fe946a10cd5e4b99fe37cb1f5b61c7d0 100644 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^K2EB~;w*-{)HT!y{qK!i~PALGR`A7}*jJDDX(MFg6?XTrl4MXV#;z z?TeS37m0f3_U~j^v#+Ow0S7Z!P56vO{o1drcSa?@{j|2q)^vkZj@O|EM~J+E>vG-s zUp`e`V7d2cTcvmXAGyG_Y_lgBBnYtafE2BZl_}S=n(y&Bvuxezcb1mx=X{dcaTTT; z=mO^}Z(~aSo!ol4@XfUHi^Xr|-$T*g_Tv2R7d5MFp5J^Hw{_3cZOC>@ELv84v3&OA twGgYo?lo{t7yYl6YYMX;=)byJ#voJe_DcV)QosOX@O1TaS?83{1OPJ_iiZFI literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^K=ba4!+xb^n#McyU@5r@Ee zg(~TXW~Qj#U77bm!Ngb7_WN{~DU;1KeUo)kW3TLPcW-c%FyLTjOFW>!qx|KJ{`4Em zs$?ea3tmwv^;2D%S4|ZpA<@FvY;Y%0^zOR&lk4^^I=}tZ*J}qd3^X|*`XpKoYFTM7 zdU9QQ?SbsF*~z~7e=1S6?#?LJt4epBb?^J`oaNU~THF7a^!z|C8_yw_?GlUfo-bNo z`Dj;~{;r(Ds;_w_Gp2wnO%MPoGH|i3zW8(1r&;NPG+#YL`!1ltNOSw z?$wSG1{^>!usyd#=FXRRe)L4#Y&rYclRpRF|Ks^dj(_#RWh!6;K?-YSNzmvv4FO#tPVdgTBB delta 313 zcmX@ibcJbxO1-qFi(^Q|t+#g^`I-y_94=lfP=4xFpjM^2Gx+Yr9g@OQ!QJokISoHu zQd9QuEA@PJ_pIsx1s;hO#%6;Afi}TeQGWM6i^=R={wI98k@+9FMAI8nAaZOxhYXIr zxc)lsi}BP4FOm;1-VwK_L}we&)&YJ=1x{v)B}xHA)tPz&P#de?^){A?B``3K6bX97o=Ge zYIuF%nV)z3>h!kEDVaUn`le0+&^n-|g)Wr~-R8Y5d3)%L7k3QM9uc5B0$=U9>*xQt zZmB~3^FKT1ZrV4gJK9uR7p^69yK#2)e&+!G_ltqLWI(2L3M~!r3;nTQcC(NIjKAp5 ZNxOZWYVVoeCQoDl0#8>zmvv4FO#qrxhJyeA delta 290 zcmX@aw3lguNF)VVV>XKLO(FwvpIVgCAQ9rGp~ zo?f`};^WA#UB^X*K;V+k)+cYvr`(;tE>q{{n#kSxH+Sm#+Q`X4B&WVqTyHibI)!sV zULGeHy!?OZaDvf9^WU>dstdh6z~H+Nf3xp?{*QNS3-oV(SSWkr*oO}jz`$?qq5tW$5})?aqNe#yDKpWQxs>Ez@`A3sb0gLgjs>^~LG>t4Gu z=eJaFceE22NEzD7_^Jh!-@hXS2A4WsDti5|t;m1w&Qknmi(XE0MTG|#*snbmJ$d>! mxx8D)zaWgv16vHVdbM5ZH7WIXhJQmDfWXt$&t;ucLK6V9n_T4p diff --git a/docs/games/Sokoban/img/Sokoban-level-Vector-5.png b/docs/games/Sokoban/img/Sokoban-level-Vector-5.png index a5287fb3009bb67dad006a325f8b7efabc636428..11f268c35e6e8df77b0708abc232f4bbb933bfb3 100644 GIT binary patch literal 279 zcmeAS@N?(olHy`uVBq!ia0vp^Q9vBP!2~450>2sqse7I-jv*Dd-rjNKYcddEz0mj3 ztw7CEc+Sc>E7#pQw=6U2Tju=ee;;77j*)Minu~=TCeYuet zs%K-8_Fikhx$oXaY$%@vGlsp7Rrv9}GO6o2pV!TazrHhQHY-ed0^2-o+3(vXtN$?L phAMxY$oZcq)e7u7kg{dZxp~e?J8WsTJqYwEgQu&X%Q~loCIC-Ma{2%O delta 258 zcmbQvG>>V5O8rw$7srr_TW{|;avd@dV7(yu(e2gLsueMw??uA%mR}H9^!2P+%EjaS zHBPsC_O<>z!^+4fV^Q$nKqK?(4M6Gi?4_UgmCY)jtc4KsR{JaIWd(L6 c$bf0*rM*|lulXLE9l-zup00i_>zopr0N^Qg{r~^~ diff --git a/docs/games/Sokoban/img/Sokoban-tile-avatar-Vector.png b/docs/games/Sokoban/img/Sokoban-tile-avatar-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 39 tcmebCo1mkV;_>@@gS3I+16JiwDTbZT7%TmB_H;4;fv2mV%Q~loCIAzb4K4rx delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh diff --git a/docs/games/Sokoban/img/Sokoban-tile-box-Vector.png b/docs/games/Sokoban/img/Sokoban-tile-box-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..f76f390fd0b4e8d0d022acd2d642d131e07e7c90 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<;VPE4bldN4_K8$r5JYAGgfX>SCe1>0#8>zmvv4FO#p|k B4_W{K delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Sokoban/img/Sokoban-tile-hole-Vector.png b/docs/games/Sokoban/img/Sokoban-tile-hole-Vector.png index dabd04345356dd4a014f1bc28dc1420c1c7a6afc..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df diff --git a/docs/games/Sokoban/img/Sokoban-tile-wall-Vector.png b/docs/games/Sokoban/img/Sokoban-tile-wall-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Sokoban/index.rst b/docs/games/Sokoban/index.rst index bf98f9af7..73a6aeeff 100644 --- a/docs/games/Sokoban/index.rst +++ b/docs/games/Sokoban/index.rst @@ -1,6 +1,12 @@ +.. _doc_sokoban + Sokoban ======= +.. code-block:: + + Single-Player/GVGAI/sokoban.yaml + Description ------------- @@ -93,6 +99,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-0.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-0.png index c1fbff298089b399bb1c5789c87178c864a75183..2e3b8596cfc7ed63bacaac72f66266f297a8a774 100644 GIT binary patch delta 187 zcmcc0c%5;AO8q=f7srr_TW@bWay1z6FdTIG^MCql{-_L%DC?ci4VJXC8Y)N4F$tG3 zI{*Ri&&EdPO_Wzj-7agJnE(b4Y`wB2lJ3gQitF`yPmdKI;Vst0Nq(nJ^%m! delta 185 zcmcc4c$IO2O8p#97srr_TW@b0@*Pp&VR3k``Tzf>_w0U+i&zYA@0OUE#Hw^PR{d#s zJF5g3{MZ|#eRyuogee=Xtu7safNGPqvyTM_KYlqcD$3|wSPn#`#E%!tOv_h#>7Th} z00AHV`DQ<@H7H#E)PorevZIX7KUv0~ns}|P5dz9R{@AjxK|q~G{f0A=le-(OW-tJO Mr>mdKI;Vst0DB-zl>h($ diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-1.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-1.png index a06cf0ecbc086a05d3916470a7e5eb50ef5c9e06..2482c4c17068e1f8011a76f906140b61c4865732 100644 GIT binary patch delta 206 zcmaFE_=<6YO8rJp7srr_TW@bUave6{adp&w@-I9rFG0U$!{jr!r#Tr(9A4gQShQk4 zH)F;HCSIv64$WdOf_Fccf8L?nFPdAu^?mQN^KV>sTRCu>WL)3?$!^y>d{j*9%=R8} zy-P7QH#^Hp8^oY`|K~}DZM$F7oVrc-@F^wTJrjl9-@andy|BPhnSnQILBH-Z`U zK5oX03rxIHTO69jUhKbiTtEGYy1e$OZ&`QErRvkvZ(U`KTHx61#sF0Ceiu)=adxW5 z+wC64b27r)9h%v6FDzh{+LHR?-nZE{qB_!TmNl=py1tsZaJ45h$iNJs^sfV{5#JNm zzd7E2@lt15DThf$BO5Q!%-4of{xJ6ngP0(NOuWBdZlvfPsk#|FblK6`u-a Q=Q99-r>mdKI;Vst0Dv`3;s5{u delta 190 zcmcc4c#Cm@O8r7l7srr_TW@bWavd<>U~$m>`+xddew~BP&Q@{V6BL`g7f6N{hQ$`f zD1yP;kn;aKjm&LIwsjtV01>L4DHpe`q9l6HDsQ=&x%o??oxs4#z_#a;<85mWel~ti z5XgM|#o#3SJ-G>B@Nb6P9$N_;a}^MHnYi(mYy0SG)@{an^LB{Ts5aK%h7 diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-3.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-level-Vector-3.png index c392c21ecbd89ef2fe3c117214e9e5a04382c820..fdf60765c01caf79a47be624d47cfdd8796cb952 100644 GIT binary patch delta 169 zcmX@Yc$9I1N_~%~i(^Q|t+%%vxf%?3SPr=S`9J+Je^iD@)SSIyg%b`4nJR1fofc(h zlLCUO_jevYUL_;@{8#Vhi${(zgT#J)eUjq7=H=7wnW4Fo{24&-qPOfw;nS(=yddy* zx?9gz-*r~{p}p)TK=8$}_r|N!$HcuJGlHa*{yOIVyenQ~gz9l5ntccx!smI8ubZ{5~>S^I3+LJ;_W zv7@|p=JE9o5XE~#UTwC|{IEGZ*dV9y4tHFSW^?=Ks z|5IP{MrCyF_uIC}LZ#cCH_fGc?yHWoToPdLW2eX7*mX z6+ Q59n70Pgg&ebxsLQ0JCLY*^QNb8zUK+!N5Lz|Gu@xiz=j&Z699MRxXu*fFJiK zUsKUdU8CRkRpprnL@D$Au#Z=+A3oU|emmOo(gDK2{CbMgzIn?oB_=??gRe_M!)>?M zsV@C`_Ovz3g!>**;hRF@y)HwQJ(wQyI9~48rAgMhP;GZ7Pve6+A)n8Vabvu~hf^wX RKY;#a@O1TaS?83{1OP=!eR%)? diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-avatar-Vector.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-avatar-Vector.png index 758055b8f4658f1313667b0074ee7ebc3484227c..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d 100644 GIT binary patch delta 39 tcmebCo1mkV^5g$}M)QQE1Ke(_Oc;tdnS|``sWdSFfv2mV%Q~loCIAvc459!4 delta 39 tcmebCo1mlg=lqBHjOGbR2e{o>FVdQ&MBb@0PBhih5!Hn delta 38 scmebEouH%m=l^_0{)D6iiK)j}7)scggo>_s-DCg)Pgg&ebxsLQ00nytr2qf` diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-box_in_place-Vector.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-box_in_place-Vector.png index c0ac2796817cbb2ec5f405e43f3a93bb6196fe93..758055b8f4658f1313667b0074ee7ebc3484227c 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJK!&-oAY8O;-t4sg4zGGQp@U=nKE+`E|p2s~Z=T-G@yGywpj Cy%3`S delta 46 zcmebCouHyA;_2cTQgJIe<;VF3b^}9$9t<{FqE(}2^C%Oy2$_pp00i_>zopr0DJcj AkpKVy delta 47 zcmebEo1mg4>gnPbQgJIeCE>^U25AGs2dv7WQVjNbjKZP&F1s-Rfv2mV%Q~loCIEPS B4+8)I diff --git a/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-wall-Vector.png b/docs/games/Sokoban_-_2/img/Sokoban_-_2-tile-wall-Vector.png index 50a43e1f746ddb37e467f9db2d780a8a55e5bc8d..5423baded82d810b8792b647a8af95a95818daab 100644 GIT binary patch delta 38 scmebCo1m+h@Z)@gw1MFRR^?DB275h5;n01T-57wt)78&qol`;+0PtW7v;Y7A delta 38 scmebCo1m-sYQVgzWFBG%)~yr>mdKI;Vst00V&xJ^%m! diff --git a/docs/games/Sokoban_-_2/index.rst b/docs/games/Sokoban_-_2/index.rst index e3a3cecc0..981af25ce 100644 --- a/docs/games/Sokoban_-_2/index.rst +++ b/docs/games/Sokoban_-_2/index.rst @@ -1,6 +1,12 @@ +.. _doc_sokoban_-_2 + Sokoban - 2 =========== +.. code-block:: + + Single-Player/GVGAI/sokoban2.yaml + Description ------------- @@ -84,6 +90,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Spider_Nest/img/Spider_Nest-level-Vector-0.png b/docs/games/Spider_Nest/img/Spider_Nest-level-Vector-0.png index cd72936aa9ec3a820afadd4f3db6ea9097a4f4ad..edbd80ecf298e65e0c205117be1e154edab23879 100644 GIT binary patch literal 1295 zcmeAS@N?(olHy`uVBq!ia0y~yV9WqwFAgT4$o|Jc`xzKmu6epRhE&{od-rzk5(ANj z#67dZ+M=9HqfduDj+>EmWBSx@i=NClAQb;&-}Q-Vor?Q=N?I=8He;+gW$w+u!LA_F zP{44Ak%JvdU0Ck^@UsZ-v}c-;2lY41nfb1i_4q#XWvAWtZ%;YSSP{b5!Ysh&V8H;T z9x@yYo*`ozdBZyV!q>kiX08mj2FX5z$wQPi_$~dh8@Zf-i;zW@5yr;mT1 z>SO+}f(hybxJ!6m7(TearEUH>+kc*$t*0)_E5~l4tq$XtHRnP$$|Ag=AOrS^f{erR zUvp$)V|UztB>eob@$tw0*>h9n|A%c26GAadhY{|3xK?m5K!r;dpD*t_#$Hmbta+ml zYBZDrEBi37T~*@t%*}6Z|C#IW%Xa=92i$n1FldN2w)D#PslNp=0vgU>D>&E-u5?UZ ztj1lgz31-aySHkpSHr!7?6m(M&Rx8CG=2B%O?ThsZC+Rgi!)?bI~!U4a?Ls``)vBw z|Nh!DqxS5BDnl{|Xm^cQVv)(;4z@?z-t$@CKYeH6>uhkef+H0YZQ$5jvChCU%S_~) zbpE<4eUK@v5bU=)-zjlZ0hc@X_Q#j^el&mnGOqaW)-dJx?Pp$hUabInS1uHq z5a8*dfpMnHTSp7&S@FmB{{Z^r@mjaFnc?tILrPf z`{c!cGkK3f{SQy_AO_q^Kn8l)!T5)T5UHiyX}4?YFO9c5PbvTGmeYKWXJ>4m{eu_gYWd y+WEjF2DcdQXM-hQo?WRpfRwo4PJbX*$8_LD<~*%~4*bA^j=|H_&t;ucLK6Vh=-D~| literal 1268 zcmeAS@N?(olHy`uVBq!ia0y~yV9WqwFAgT4$o|Jc`xzKmc6+)whE&{od-r1QZ37X9 zz>OcY9_r0=+gu^_)`|b{ACZ^-&X!>+UH;GiaFr$o?Of2nZh6&+@y`i)Zw3x_1(}8d zhC_@T>`=;JS@iFsUlk_nu9-w1RL+_4Zfo<#rN3^xocy}D>=A>_DzI7w87KwR^l{15 z^1d|h5~k>T+}Z3uzxD6849zJL`LKcsYB<~umz1IshVLuQ*6FG4{eSV>^!Fm_d(g6&gJ25w&Xf&0+esa9Bn`qcWX^PP(~ z8C-RLoK<@+|NnOF`E#Cm=Q?e_dg6EOa$e_^s@8=Ja-mSqBf_8}D7SxFDlm0uS8sVb zb*fp#7yAd_w!E%A9Gi|cjj*H?nJ`RVyzP1?GdKl70|Od3A11y28@1On^4O&FKc+uA z!dMZ)h#u`IQGiGdes9w_7k!orodZiMa3=y8KTbc%U`TT=d<0HJ$TBThf*mZ69D?v9 z*TTGGLC5~E_#ExRJIinF*yVE)yDxSbs=J?UJ9aI*{^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ delta 39 tcmebCo1mkV^22^2qj^Ho0dBWdCJgTc7?=1aEJzmvv4FO#llj4AuYu diff --git a/docs/games/Spider_Nest/img/Spider_Nest-tile-lava-Vector.png b/docs/games/Spider_Nest/img/Spider_Nest-tile-lava-Vector.png index 0ae9106602e30ef63317c132966ce0d9f4203e76..d8a72ac8469cd31b3e1cd0c3c146edacbaebc011 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PklEde?n4%#MEOf4DQX0Zy)VqjbQ)+Pgg&ebxsLQ0C^7& Ax&QzG delta 47 zcmebEo1mg4>gnPbQgJK!&-sFGM)QQE1Ke(_Oc>l&GQQzmvv4FO#m9M4KM%z delta 39 tcmebCo1mlgCE>^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ diff --git a/docs/games/Spider_Nest/img/Spider_Nest-tile-spider-Vector.png b/docs/games/Spider_Nest/img/Spider_Nest-tile-spider-Vector.png index 3b74da918448f709a894ced7702d2a4410a2d434..95e86ee990c447aca173c846e06f8d74551c2692 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe<%j)5M)QQE1Ke(_Oc>q?FfQ>;Sdzd11fH&bF6*2UngE1$ B55WKc delta 46 zcmebCouHyA;_2cTQgJK!%a8L7>;{GgJzmL74AVs!qczopr0EWX5 AD*ylh diff --git a/docs/games/Spider_Nest/img/Spider_Nest-tile-wall-Vector.png b/docs/games/Spider_Nest/img/Spider_Nest-tile-wall-Vector.png index d8a72ac8469cd31b3e1cd0c3c146edacbaebc011..3b74da918448f709a894ced7702d2a4410a2d434 100644 GIT binary patch delta 38 scmebEouH%m<;VF3b^}9$9mdKI;Vst00NT?&Hw-a diff --git a/docs/games/Spider_Nest/index.rst b/docs/games/Spider_Nest/index.rst index 856034e95..0c3ba28b9 100644 --- a/docs/games/Spider_Nest/index.rst +++ b/docs/games/Spider_Nest/index.rst @@ -1,6 +1,12 @@ +.. _doc_spider_nest + Spider Nest =========== +.. code-block:: + + Single-Player/GVGAI/spider-nest.yaml + Description ------------- @@ -50,6 +56,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Spiders/img/Spiders-level-Vector-0.png b/docs/games/Spiders/img/Spiders-level-Vector-0.png index a9e9af837f8e417e4547be9b6a13b5f3ecaae050..e63144536439e22b0f4482f199c23b70d11bd919 100644 GIT binary patch delta 158 zcmdnZxQlUuN`0fJi(^Q|t+%%hayA(7uwHPvb7F3Z+FZ}{OT3R9wZ!i4fA&eE(LdAp znQ_%=0a-RFAoz7#GXL4@AfEGgq&9APllhvF7YP2ADQ|mi17Y92D!cKF;8oweu98a^ zKwy8y5%sy-ub-dZ{k5SD2c6 zH-EJ%lN1P4<*Y5W-pqAIw@B#qn`6u%p0}r`BC_a0UViXZItl_rm#v6Lt2wF#v(5tDnm{r-UW| DAe=#a diff --git a/docs/games/Spiders/img/Spiders-level-Vector-1.png b/docs/games/Spiders/img/Spiders-level-Vector-1.png index 6f1ed0cfb4663ccc091c175f960ae0bccc51eff5..6a9a907890354c93a262746ca49dfce4507bb8f7 100644 GIT binary patch delta 164 zcmX@lc%E^Bas2`7WcP1NOf!9>CYLoBx(OQpetNj2I)@{rr}~d}>>oW3Fo^f6-(=_C zdenBh*6edhj}0pz!lt*K*Va9lG64*ta{0?QxxQ>m=qT}mNZ&f_{rA8FMKIWUOK#ro u*of2qmg>#zEg%r0`|>=itPmJnS}JqnK7YKYfO7)_5O})!xvXxkN?) delta 164 zcmX@lc%E^BalOO28xw6Kr|2g7-#DmxOd%<4f6a`7eqOIDVRh4%il~6WlDf#xPdnDc z-6U4dS27N^O8PH0E1m>dv-d=I)TBisoaxN1vR(5^tr@843jG|f8`qC%pgZE0xn=PU27c^y6g0meFOXW3rK yLsoHB+x_RxC(9BOz~F)6q<^0ncp%`8hWY9=!Mk2|59R{h&*16m=d#Wzp$Pzpms~~w literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51SA=YQ-1-e?Vc`;q76J!NJ87@+aqsLA5AE yru})bCUoYlQ+JK_n?MCimmbyThuNNH1lw;bjyj+h7(8A5T-G@yGywo2w3catZv92xCIb=H3wDRx-l!O#^sy4wb6CY`_O`jW{-^a$-#p<98vT1uSW)voJMcYkuqf=&8I~|F7<<&T#>dVJtwxS^dxA$xvM^ eK$mz;;lF=L#P;#^H%1IV;OXk;vd)Pqp$PzY>v0nR delta 265 zcmZ3>w3catZvAP_76$>AfNDeKVxtwlC(ma3I7l6oJ$5ZTJilK`|?xe{-6&hH% z#dIPz2pniKwJNT&RFeL2T7>`c57V;KS}+0z8Pp1EBCYm zO{}ZGHND@y`Tfr+d(uEcVmghi++lB7<3HwxoQ3hl)_m9d(bZ}CJK@Q+x@z~$GR`n7 qdgZtLd$A-EXdzH7BhY59RCddH8OvYgtG_S+fv2mV%Q`2fgeCx_vu=R^ diff --git a/docs/games/Spiders/img/Spiders-level-Vector-4.png b/docs/games/Spiders/img/Spiders-level-Vector-4.png index 8879ad4b3fe0a2f651b0bba9893366d819ef38ea..d5f4a87d14b55c1b5ad620e74e235c970462ecd6 100644 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^`+#^02NRH7+RM0xfr0U`r;B4q#jUq@9Jvk|2sm7H zDN=raWuE8xCFxz#;$@RHW;hwW&*cs}l+Kl$=@;$$VRyH(gOvySO9j~pe3u-pJlZ|H zp8v|tk6H8c9gFz1+5b&uSXCq_$}VXxfhu!)nKSuqw_zznIXh4S$YKQQ2eZH?0a*{U zQ}6iO2fw!Fygp^_zLjv@U|naD9++9)+Gla=+np;wE0HV@=u-YMJ>zz9Fh~qx=MrX+ z(-7*|`D)B>+Rlc!3@-QL^WBv1Ij`_)ggDpH3hG>-!wdS19{ZmEQJNeK^$S#T#;qy0 zQ+_9bU4rndMPwn%{9}8+?z#8-$}(_(vA=8pg@B_~OVaEbgS^$dvC7EYT5P8Oei@%! r1hO~o72R4Dd;k&#P*XhEZ^czDmEF3Od#(vERvA29{an^LB{Ts5gmcdm literal 513 zcmeAS@N?(olHy`uVBq!ia0vp^`+#^02NRH7+RM0xfr0Uar;B4q#jUq@9J!he1RO5< zoa}uy($teupK~lID^Le3uwsD#!v^CzgDZ zcmFc&>~|r*4~MR$y?SuS$;yNMWdlfwqt%9+H`Q*7C%*zKoxlf_0J4~Y5?~fs36OOl z{8gpBZ0Zsnmcfm70I4$Q65f9D=C`9aw><~SfHgwRUJ@jGFmO}ud0&t-5DtRb z(p&=8uOR#5LH*>tn^!h-a|11hTe&YH>itKNK8^KTXIkyp02W4ZBwRbpxlmg@*q0n` zeAS_E`)I%PY%8c1NMIbk&~^vt+u2C|2g)CwAL{<$-JbRH)_=F%x)SVCN0`2qM=n+M zFE4PZTzmG)!PZtJUnD%f{p3p0JKdh?Ld*E{bih7@hMvCleYIvZgQf({%UY&)_1G%+ RXTYds@O1TaS?83{1OWR7-7)|G diff --git a/docs/games/Spiders/img/Spiders-tile-gem-Vector.png b/docs/games/Spiders/img/Spiders-tile-gem-Vector.png index dabd04345356dd4a014f1bc28dc1420c1c7a6afc..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df diff --git a/docs/games/Spiders/img/Spiders-tile-gnome-Vector.png b/docs/games/Spiders/img/Spiders-tile-gnome-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..f76f390fd0b4e8d0d022acd2d642d131e07e7c90 100644 GIT binary patch delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh diff --git a/docs/games/Spiders/img/Spiders-tile-spider-Vector.png b/docs/games/Spiders/img/Spiders-tile-spider-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Spiders/img/Spiders-tile-wall-Vector.png b/docs/games/Spiders/img/Spiders-tile-wall-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Spiders/index.rst b/docs/games/Spiders/index.rst index 42b046d90..ca7aafdd8 100644 --- a/docs/games/Spiders/index.rst +++ b/docs/games/Spiders/index.rst @@ -1,6 +1,12 @@ +.. _doc_spiders + Spiders ======= +.. code-block:: + + Single-Player/Mini-Grid/minigrid-spiders.yaml + Description ------------- @@ -90,6 +96,9 @@ The most basic way to create a Griddly Gym Environment. Defaults to level 0 and env.render() # Renders the environment from the perspective of a single player env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() Objects diff --git a/docs/games/Zelda/img/Zelda-level-Vector-0.png b/docs/games/Zelda/img/Zelda-level-Vector-0.png index a53b664ae1dca343bb0c7810b4aadfeee943058c..003660de487bafc23c4a67c64d4b3cc8058396c2 100644 GIT binary patch delta 233 zcmbQsG?!_DuERyn76So?gKL&d`;ugOEy?s-lJUl7jSPXad*Us>$e1al?^scN>9ubj zBU|DD1s;hO#%6<>g*8QccDV9yoz2!gX=0&pJy47%c;DxTE3zMF^e(;ZdORxndF&w* z4raKXT{aWuz3Kku_C2_A@$E$_5OWd)*mw>#I7%2m6+WKw|HsmO-#_olgxQoJ(DvWn z_58}M_LbiGZ~gU_ArwnoS^S~?B3M8UYEQ{VW~Ms9>|go!^cjG_)78&qol`;+07T`*}nJdMc4ET^RXzOj=QqEKf1wD!hnOBE%AT?&;J11Uk9Ja z#a_vivYFDt*lds>z{Yb3Nr6PmL*1*!>h9}$7fYUw4ZmB*S!=|LP_Q7nOD12nHsjul zZvjH+I>E-ZT@N|_+q?MWv5RY4GLZ}~y|E}c_wDMPUv7ra->E(uNw36$4|_8%EAU9b d-1BRrer%cG-|tM%%Ygo6@O1TaS?83{1OO(tXb1oR diff --git a/docs/games/Zelda/img/Zelda-level-Vector-1.png b/docs/games/Zelda/img/Zelda-level-Vector-1.png index e9dde405b78cc297e7fb7c47a7ae787eeb0fa723..8b83dba307e1050a06779c1bc56ef9b8010a86ec 100644 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^Kc`>+%`4%%M8BvrN(7P4;#5gPibLnHb@Wvsww!PX|*nQpLJ~U z)84eMZ3tr@eh4nvy!XY`cdKrG$wQVnC{nv>Rb78BvRLB(ucgbkOCJ3E_sOhJdta1S zrm5HO;T2N_`9cC@m&~3I^%w7feFf$xT&XnfuoHei4;c)I$ztaD0e0sxCN Bkfi_s literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^K>ba4!+xb^n-LEdHsk%q*$ z$2#vG8I_9d?%BPAXYUstcBc!U{$4lB_@i`^JNHS6YVW(Jf(Zg_Jck+_B@8$Y3rA(e zN90#rp4fkAx#c%M?d9ipm)y1O%8tLsHhYo*OyRt^E9Vwn&%2y+xU~O{!P&l3BDujr za5L@%f6-;XB$B>fvf6v;)bmetZY@*ck!WFT2I_Cyx%|b6^h~9<-)_I0Hue21O=P+H z`HPOvb^X3;b>ZdR$P#S9Uqbj-zx;n=Sq73Lp3Pfy`=9!qqRZ~fr=Cu~xV8Ar+1m+7 p&Ug~MWe+dZS~eazMz+7dnDlDx%FnO3#|#WF22WQ%mvv4FO#tjskRSj6 diff --git a/docs/games/Zelda/img/Zelda-level-Vector-2.png b/docs/games/Zelda/img/Zelda-level-Vector-2.png index c499a8fe15219cde54bd1406c80d8f5959ce4462..0bfdce025c4d41d760a4d30e5446948663aee9eb 100644 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9<2r;B4q#jUq@5Aq%|;BmMp zl`2-2IP2V{%w68o45eFwU$^Wl?-dBW5MH#wE9HKJlYTUV5{n=cGX9=rf9?MHTYV>z zH_X~7ZX5nxdF@(j&V!{Q0*W012<)SB$$%~9>=B+FeJI+MT4ruprQfH_zItc-{WPXy zE9bN*I60u;5AA!B+15RmHGW{l)gj=*fxu~(au`KmH691oKS+^Kw0EA9dn|N2PVKRzsAe-f$ aaBtz1kYBkf?FKLu89ZJ6T-G@yGywqM)_oBG literal 371 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9;xr;B4q#jUq@4{|aY3OHQs zSu*YI(X4IL)*g$y(VU)^mkQ(r1` zas9#7w&zsuzl!|z;A%MM{>NWtd2-0PGYU3wAh45M;r8Tb1#TZq&C?D%)=+Q~P()yl zC3?S8#7i%2zI{vu%@U;+-L<<7AN%=Ky*o0aMZw7d1#ejTietw4Z&H&5TsS(Auo8<{ z*Sq%<>*9)8)R63f*!jWII?e1s*kzu=-fxe-6z>sVKC|UPp@@KDhX4XUDcqgRX29v# f(!ha?6SeaX2TT6_bxEom7?=#6u6{1-oD!MgnPbQgJIeWyX*BjOGbR2e{o^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ delta 39 ucmebCo1mkVGULa5M)QQE1Ke(_Oc>rSVqDVkxc37C5O})!xvXzmvv4FO#m9M4KM%z delta 39 tcmebCo1mlgCE>^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ diff --git a/docs/games/Zelda/img/Zelda-tile-wall-Vector.png b/docs/games/Zelda/img/Zelda-tile-wall-Vector.png index 0ae9106602e30ef63317c132966ce0d9f4203e76..d8a72ac8469cd31b3e1cd0c3c146edacbaebc011 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PklEde?n4%#MEOf4DQX0Zy)VqjbQ)+Pgg&ebxsLQ0C^7& Ax&QzG delta 47 zcmebEo1mg4>gnPbQgJK!&-sFGM)QQE1Ke(_Oc>l&GQQ$e1al?^scN>9ubj zBU|DD1s;hO#%6<>g*8QccDV9yoz2!gX=0&pJy47%c;DxTE3zMF^e(;ZdORxndF&w* z4raKXT{aWuz3Kku_C2_A@$E$_5OWd)*mw>#I7%2m6+WKw|HsmO-#_olgxQoJ(DvWn z_58}M_LbiGZ~gU_ArwnoS^S~?B3M8UYEQ{VW~Ms9>|go!^cjG_)78&qol`;+07T`*}nJdMc4ET^RXzOj=QqEKf1wD!hnOBE%AT?&;J11Uk9Ja z#a_vivYFDt*lds>z{Yb3Nr6PmL*1*!>h9}$7fYUw4ZmB*S!=|LP_Q7nOD12nHsjul zZvjH+I>E-ZT@N|_+q?MWv5RY4GLZ}~y|E}c_wDMPUv7ra->E(uNw36$4|_8%EAU9b d-1BRrer%cG-|tM%%Ygo6@O1TaS?83{1OO(tXb1oR diff --git a/docs/games/Zelda_Sequential/img/Zelda_Sequential-level-Vector-1.png b/docs/games/Zelda_Sequential/img/Zelda_Sequential-level-Vector-1.png index e9dde405b78cc297e7fb7c47a7ae787eeb0fa723..8b83dba307e1050a06779c1bc56ef9b8010a86ec 100644 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^Kc`>+%`4%%M8BvrN(7P4;#5gPibLnHb@Wvsww!PX|*nQpLJ~U z)84eMZ3tr@eh4nvy!XY`cdKrG$wQVnC{nv>Rb78BvRLB(ucgbkOCJ3E_sOhJdta1S zrm5HO;T2N_`9cC@m&~3I^%w7feFf$xT&XnfuoHei4;c)I$ztaD0e0sxCN Bkfi_s literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^O+XyQ!2~3K^GLp6U|^K>ba4!+xb^n-LEdHsk%q*$ z$2#vG8I_9d?%BPAXYUstcBc!U{$4lB_@i`^JNHS6YVW(Jf(Zg_Jck+_B@8$Y3rA(e zN90#rp4fkAx#c%M?d9ipm)y1O%8tLsHhYo*OyRt^E9Vwn&%2y+xU~O{!P&l3BDujr za5L@%f6-;XB$B>fvf6v;)bmetZY@*ck!WFT2I_Cyx%|b6^h~9<-)_I0Hue21O=P+H z`HPOvb^X3;b>ZdR$P#S9Uqbj-zx;n=Sq73Lp3Pfy`=9!qqRZ~fr=Cu~xV8Ar+1m+7 p&Ug~MWe+dZS~eazMz+7dnDlDx%FnO3#|#WF22WQ%mvv4FO#tjskRSj6 diff --git a/docs/games/Zelda_Sequential/img/Zelda_Sequential-level-Vector-2.png b/docs/games/Zelda_Sequential/img/Zelda_Sequential-level-Vector-2.png index 7073dc2bc7468d50d554f2a555c7ff407be4d96e..ab78f94137b5a0f75d1159f54ed6dc0105a98507 100644 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9;nr;B4q#jUq@FY-1kh&Tks z9oBjG$mHB3lXr)V)=NKdUz}k3e7cF$E$JT$CYwYP7W8W}2sUvrBH_mBtCK%IjlC?m zv)s+tZ2ydh*Cx%qxcEqq{@pb#3D#U40xlc~tR|#henw{*N8Pg{JU9B7lvo52xMk** zK>ohH<-s0#4|mUOcpMVuB%r8(f`2%_o6-1sXEIxkJF*iP1rPbG(oA4s-@2wn!N~!N zA({<(gpJQ%pZDXEp8h5wgv%XU8aNzV4jA~IvAKVmZ@$aBxfgrhE_%LB)q1ge=e_p7 m+QOL5b1|FDfa#`+iTcG-GV-hR7+wK`mBG{1&t;ucLK6U2D1CVV literal 386 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!qd1s=WUufaFQ9;@r;B4q#jUq@FY-1Sh&Wud zJFN5Wk;&Ypmv?wiugnYZ>Dh9?a{r8kF7y8WWp2~r8Y<gTH z^4z%XENW=x2`GNBO+OQJU~ieJ>$Zbedw#9|z3t*oi^${8AC*iN*we?P#3G2mEv0Yf k88XjkQE+lV!3CP~dH-eq>de^Y1`JyUPgg&ebxsLQ0M#Ie#Q*>R diff --git a/docs/games/Zelda_Sequential/img/Zelda_Sequential-tile-avatar-Vector.png b/docs/games/Zelda_Sequential/img/Zelda_Sequential-tile-avatar-Vector.png index d8a72ac8469cd31b3e1cd0c3c146edacbaebc011..09f573db490cc852d7fcc7ffff6aea714d6c4e7a 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIeWyX*BjOGbR2e{o^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ delta 39 ucmebCo1mkVGULa5M)QQE1Ke(_Oc>rSVqDVkxc37C5O})!xvXzmvv4FO#m9M4KM%z delta 39 tcmebCo1mlgCE>^U25AGs2dv7WQVi3?7^AmmIo)Ca0#8>zmvv4FO#mF{4L|?@ diff --git a/docs/games/Zelda_Sequential/img/Zelda_Sequential-tile-wall-Vector.png b/docs/games/Zelda_Sequential/img/Zelda_Sequential-tile-wall-Vector.png index 0ae9106602e30ef63317c132966ce0d9f4203e76..d8a72ac8469cd31b3e1cd0c3c146edacbaebc011 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJK!PklEde?n4%#MEOf4DQX0Zy)VqjbQ)+Pgg&ebxsLQ0C^7& Ax&QzG delta 47 zcmebEo1mg4>gnPbQgJK!&-sFGM)QQE1Ke(_Oc>l&GQQGdmh^WeMsFPV3&@n^w z*CJNNj0;S>Qd=el$j1Cj&fdQ`x@Y^!U9Ph-!rL91*>o=~V3jh-Xaq4Fxj_uxC@=%4 z2go>o<*ZreyKw1+rC)CCT~k$Glr|HrPb>qZZ;PDG;$M3g)ud-Eij|!>QJBw>`wPQT i`@6GYHp+?H7OKyb=6$bzn)4F_5O})!xvX7zRCXC+8Oq@3Sja QR{#J207*qoM6N<$f~A>9H2?qr diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-1.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-1.png index 0ce964f17db99b0d7060fd8d1512a22e17bfcf7c..8de5045b96e19ffc012698bedb98d4746e4f7956 100644 GIT binary patch delta 223 zcmZ3-w3catip3XC7srr_TW{}PMwU+-3_sgnvvn&?$FGpdtm{q)RwawYG&?z ztEc3S9^V6x&HH-_vwpZDi=8F z-#j3Dy1Y&8)B3x0{)``Lj32s~Z=T-G@y GGywo7rC%ig delta 213 zcmZ3>w2oRO$X@7iThtHO^&I=ry#WF51@!mRF@FQyuf4H}t+O2Df7Z$Ke znPfDwtv%`A7C-l|j|oUg6iDq0OaHd7A1^Oejk+5)|2#;hkqsonZSq31J7>q1?>xR+ z)-rE#0GSFj1ju01g_`zm--qSZjKUKGlqD9_-{DPqD%>l#d}9#<5O})!xvXdoQ*ym}E4v@kT9hiDgB0IQ<zopr0AFx=t^fc4 literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1SGeyEo^0AU}W-iaSW-r_4e*V&SnFFhCqLr zlQT=y=AN8;j+aH$wDbM%=nY>AS^6fKZMrP|mBXP~EaSogR;eux&1||LhDk;vh~dZ$ zV(>-*86};(=WVTuR`n?-n_leWgNmAD%uZy@-6*ri>RHYrSy?8KQjl3$HO4x{-;FOK zOt^iAXZzv#da{pyNnii5cYY_r_`4|+`R#tqu5K$Y^ZU*NG6dufpdt1-8Sj;MroVV0 rkZ}Pbcx~k&`@6GYJbZ5c`?zcwzl3IRa1|>sP#8R2{an^LB{Ts5>-==_ diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-3.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-3.png index 34f645e64983e076d790d80e8bce2cbb375cbe95..b5c55669deba0417109ddf5bf0273dccfe5a5ea4 100644 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^6+oQF!2~3eI~|xA7#KM{T^vIyZoR#Gn)i@_fNLPX zmwSM)YoPEIlQ)rPl6nFsoWJ||VqnTkO+gpFzl*q;E-YY`GRbIU)4j03ksHL|jRG?+ zFo77&Vi^~hYLE4m=Kq!Ys~&2+`0uTbf6wFGcPx^XW#X0E;s8=G?cw!R*JJnIPrG*a z$}^tuEsLF@8hLLS{gLqcp0)7l;{AV@eT)7(ce&Z}>b5>Um@O~fc9rh;{dMMFU3BF!Z*OnpJZvD~5Ey&H z?VHa>Fr04H%f%_ly7xZY&I*|(XO&Ga`s6qqn#D3MEMS$|;?T^d3u2gLG=dn8+#m*T z6p-k+gvSoHr?1y`0XnJn7+8R65s6)uYR)N@7m?R&h4CPaB&?U zRI5qG?1%5=LjP9w|60CRa#lt-JIwSi|MS~yD~(NnYA-N>tZ5c|an;;8zfJAdRYs7l qCK(r)_WnxK5atEBjesMUzvou(QMuf-nmrU41`M9AelF{r5}E+Oa(9vd diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-4.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-level-Vector-4.png index 2c7275f1575724a5247b27e71a2a179b763717eb..5c15184cebdceb7898ca63bd9192aa58a5a8a8c7 100644 GIT binary patch delta 235 zcmX@XbeL&^iZ++0i(^Q|t+#hCayBT4I9!a|5@>B2V!U;7uELb4MbH0TKknGl?m1Dc z_F~t>V3qpsH?F1EXtK-;+5Ls&@7g5LG5tX)H8LOM_-#yx5;+B zZN(yGlF`VfdtrfZo2mB93%_gvw=8nxHp#fa#2dB1@w|oPv5B^Gt)u6j>opIJy0`!= z#Q`+wMP9ar>)+Dz8&X!4oWF2Q@d8+?k?3lsc1+4cW^&+PAyh5jz?*Nje8%b&c< zibV>f9;ha_O?mcL>%(WlGL1`0IZQytM=fx?EzWaX?(NKlon@sBU@N7zIQ;*frXf6; gn^92;sABqi-pvmdKI;Vst0KR}|%>V!Z diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-avatar-Vector.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-avatar-Vector.png index 3220f5d4c4685edb3f04f74a03286533d1ee3f7b..dabd04345356dd4a014f1bc28dc1420c1c7a6afc 100644 GIT binary patch delta 47 zcmebEo1mg4>gnPbQgJIe#pCz+25AGs2dv7WQVctvF;@EN?CE3x0#8>zmvv4FO#qDI B5E%df delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-ground-Vector.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-ground-Vector.png index f76f390fd0b4e8d0d022acd2d642d131e07e7c90..a3bc6781331d3ef85577988b9700cad2a055d450 100644 GIT binary patch delta 39 tcmebCo1mj)bNIu2M)QQE1Ke(_Oc+cn8Sg9oobAm31fH&bF6*2UngAHl4RZhh delta 39 tcmebCo1mkV@?-w725AGs2dv7WQVhH587sG`t4S~bfv2mV%Q~loCIAj%3~vAc diff --git a/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-rock-Vector.png b/docs/games/Zen_Puzzle/img/Zen_Puzzle-tile-rock-Vector.png index a3bc6781331d3ef85577988b9700cad2a055d450..3220f5d4c4685edb3f04f74a03286533d1ee3f7b 100644 GIT binary patch delta 46 zcmebCouHyA;_2cTQgJKU=I?w){)D6iiK)j}7)+fR@BOc0+rzopr0B$%B AX8-^I delta 47 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 literal 0 HcmV?d00001 diff --git a/docs/games/img/Robot_Tag_8v8-taster.png b/docs/games/img/Robot_Tag_8v8-taster.png new file mode 100644 index 0000000000000000000000000000000000000000..fa3426c08cab8803928c76ed3a737b67cf41db45 GIT binary patch literal 40453 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 literal 0 HcmV?d00001 diff --git a/docs/games/index.rst b/docs/games/index.rst index 0a8cac5de..86730abcc 100644 --- a/docs/games/index.rst +++ b/docs/games/index.rst @@ -266,3 +266,40 @@ RTS - +*********** +Multi-Agent +*********** + +.. toctree:: + :hidden: + + Robot_Tag_8v8/index + Robot_Tag_12v12/index + Robot_Tag_4v4/index + +.. list-table:: + :class: game-gallery + + * - **Robot Tag 8v8** + + .. image:: img/Robot_Tag_8v8-taster.png + :target: Robot_Tag_8v8/index.html + :width: 200 + + Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + - **Robot Tag 12v12** + + .. image:: img/Robot_Tag_12v12-taster.png + :target: Robot_Tag_12v12/index.html + :width: 200 + + Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + - **Robot Tag 4v4** + + .. image:: img/Robot_Tag_4v4-taster.png + :target: Robot_Tag_4v4/index.html + :width: 200 + + Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + + diff --git a/docs/index.rst b/docs/index.rst index 0dc655aed..11b25b705 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,6 +31,7 @@ Griddly documentation. getting-started/gdy/index getting-started/action spaces/index getting-started/observation spaces/index + getting-started/visualization/index .. toctree:: :maxdepth: 2 @@ -39,6 +40,17 @@ Griddly documentation. games/index + +.. toctree:: + :maxdepth: 3 + :caption: Reinforcement learning + :name: sec-rllib + + rllib/intro/index + rllib/single-agent/index + rllib/multi-agent/index + rllib/rts/index + .. toctree:: :maxdepth: 10 :caption: Tutorials diff --git a/docs/reference/GDY/Environment/Observers/Block2D/TileSize/index.rst b/docs/reference/GDY/Environment/Observers/Block2D/TileSize/index.rst index 1fbf0de08..bbe22b600 100644 --- a/docs/reference/GDY/Environment/Observers/Block2D/TileSize/index.rst +++ b/docs/reference/GDY/Environment/Observers/Block2D/TileSize/index.rst @@ -5,7 +5,7 @@ Tile Size ========= -:Description: Configuration options for sprite 2D renderer. +:Description: Tile size to use for blocks. :Possible Values: diff --git a/docs/reference/GDY/Environment/Observers/Block2D/index.rst b/docs/reference/GDY/Environment/Observers/Block2D/index.rst index 415e0661f..b0622fe25 100644 --- a/docs/reference/GDY/Environment/Observers/Block2D/index.rst +++ b/docs/reference/GDY/Environment/Observers/Block2D/index.rst @@ -5,7 +5,7 @@ Block 2D ======== -:Description: Configuration options for block 2D renderer. +:Description: Configuration options for the block 2D renderer. .. list-table:: diff --git a/docs/reference/GDY/Environment/Observers/Sprite2D/TileSize/index.rst b/docs/reference/GDY/Environment/Observers/Sprite2D/TileSize/index.rst index 1fbf0de08..19349bb28 100644 --- a/docs/reference/GDY/Environment/Observers/Sprite2D/TileSize/index.rst +++ b/docs/reference/GDY/Environment/Observers/Sprite2D/TileSize/index.rst @@ -5,7 +5,7 @@ Tile Size ========= -:Description: Configuration options for sprite 2D renderer. +:Description: Tile size to use for sprites. :Possible Values: diff --git a/docs/reference/GDY/Environment/Observers/Sprite2D/index.rst b/docs/reference/GDY/Environment/Observers/Sprite2D/index.rst index 7e60c6366..e4fb83b22 100644 --- a/docs/reference/GDY/Environment/Observers/Sprite2D/index.rst +++ b/docs/reference/GDY/Environment/Observers/Sprite2D/index.rst @@ -5,7 +5,7 @@ Sprite 2D ========= -:Description: Configuration options for sprite 2D renderer. +:Description: Configuration options for the sprite 2D renderer. .. list-table:: diff --git a/docs/reference/GDY/Environment/Observers/Vector/IncludePlayerId/index.rst b/docs/reference/GDY/Environment/Observers/Vector/IncludePlayerId/index.rst new file mode 100644 index 000000000..26198d469 --- /dev/null +++ b/docs/reference/GDY/Environment/Observers/Vector/IncludePlayerId/index.rst @@ -0,0 +1,17 @@ +.. _#/properties/Environment/properties/Observers/properties/Vector/properties/IncludePlayerId: + +.. #/properties/Environment/properties/Observers/properties/Vector/properties/IncludePlayerId + +Player Id +========= + +:Description: Includes the player id in the vector representation of each tile. + +.. list-table:: + + * - **Data Type** + - **YAML Key** + * - boolean + - ``IncludePlayerId`` + + diff --git a/docs/reference/GDY/Environment/Observers/Vector/IncludeRotation/index.rst b/docs/reference/GDY/Environment/Observers/Vector/IncludeRotation/index.rst new file mode 100644 index 000000000..25aa089cd --- /dev/null +++ b/docs/reference/GDY/Environment/Observers/Vector/IncludeRotation/index.rst @@ -0,0 +1,17 @@ +.. _#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeRotation: + +.. #/properties/Environment/properties/Observers/properties/Vector/properties/IncludeRotation + +Rotation +======== + +:Description: Includes the rotation of the object in the vector representation of each tile. + +.. list-table:: + + * - **Data Type** + - **YAML Key** + * - boolean + - ``IncludeRotation`` + + diff --git a/docs/reference/GDY/Environment/Observers/Vector/IncludeVariables/index.rst b/docs/reference/GDY/Environment/Observers/Vector/IncludeVariables/index.rst new file mode 100644 index 000000000..fd69bb928 --- /dev/null +++ b/docs/reference/GDY/Environment/Observers/Vector/IncludeVariables/index.rst @@ -0,0 +1,17 @@ +.. _#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeVariables: + +.. #/properties/Environment/properties/Observers/properties/Vector/properties/IncludeVariables + +Variables +========= + +:Description: Includes the value of variables in vector representation of each tile. + +.. list-table:: + + * - **Data Type** + - **YAML Key** + * - boolean + - ``IncludeVariables`` + + diff --git a/docs/reference/GDY/Environment/Observers/Vector/index.rst b/docs/reference/GDY/Environment/Observers/Vector/index.rst new file mode 100644 index 000000000..005ecd406 --- /dev/null +++ b/docs/reference/GDY/Environment/Observers/Vector/index.rst @@ -0,0 +1,37 @@ +.. _#/properties/Environment/properties/Observers/properties/Vector: + +.. #/properties/Environment/properties/Observers/properties/Vector + +Vector +====== + +:Description: Configuration options for the vector renderer. + +.. list-table:: + + * - **Data Type** + - **YAML Key** + * - object + - ``Vector`` + + +:Properties: + +.. list-table:: + + * - **Property** + - **Required** + * - :ref:`IncludePlayerId <#/properties/Environment/properties/Observers/properties/Vector/properties/IncludePlayerId>` + - + * - :ref:`IncludeRotation <#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeRotation>` + - + * - :ref:`IncludeVariables <#/properties/Environment/properties/Observers/properties/Vector/properties/IncludeVariables>` + - + + +.. toctree:: + :hidden: + + IncludePlayerId/index + IncludeRotation/index + IncludeVariables/index diff --git a/docs/reference/GDY/Environment/Observers/index.rst b/docs/reference/GDY/Environment/Observers/index.rst index 21f42ad20..d5578d3e9 100644 --- a/docs/reference/GDY/Environment/Observers/index.rst +++ b/docs/reference/GDY/Environment/Observers/index.rst @@ -21,6 +21,8 @@ Observers * - **Property** - **Required** + * - :ref:`Vector <#/properties/Environment/properties/Observers/properties/Vector>` + - * - :ref:`Sprite2D <#/properties/Environment/properties/Observers/properties/Sprite2D>` - * - :ref:`Block2D <#/properties/Environment/properties//Observers/properties/Block2D>` @@ -32,6 +34,7 @@ Observers .. toctree:: :hidden: + Vector/index Sprite2D/index Block2D/index Isometric/index diff --git a/docs/reference/GDY/Environment/Termination/End/index.rst b/docs/reference/GDY/Environment/Termination/End/index.rst new file mode 100644 index 000000000..5c1674eee --- /dev/null +++ b/docs/reference/GDY/Environment/Termination/End/index.rst @@ -0,0 +1,32 @@ +.. _#/properties/Environment/properties/Termination/properties/End: + +.. #/properties/Environment/properties/Termination/properties/End + +End Conditions +============== + +:Description: If any of these conditions are met, the game will end. + +.. list-table:: + + * - **Data Type** + - **YAML Key** + * - array + - ``End`` + + +:Array Type: + +.. list-table:: + + * - **Type** + - **Description** + * - :ref:`Termination Conditions<#/properties/Environment/properties/Termination/definitions/terminationCondition>` + - When a termination condition is met, the game will reset itself. If there are multiple players, the termination arguments are expanded internally "per player". This can be used to find the first player to a certain number of objects, or the first player to reach a certain score + + +.. toctree:: + :maxdepth: 5 + :hidden: + + /reference/GDY/Environment/Termination/terminationCondition/index diff --git a/docs/reference/GDY/Environment/Termination/index.rst b/docs/reference/GDY/Environment/Termination/index.rst index 8371b82a8..f79bb0d12 100644 --- a/docs/reference/GDY/Environment/Termination/index.rst +++ b/docs/reference/GDY/Environment/Termination/index.rst @@ -25,6 +25,8 @@ Termination - * - :ref:`Win <#/properties/Environment/properties/Termination/properties/Win>` - + * - :ref:`End <#/properties/Environment/properties/Termination/properties/End>` + - .. toctree:: @@ -32,3 +34,4 @@ Termination Lose/index Win/index + End/index diff --git a/docs/requirements.txt b/docs/requirements.txt index ff6a6c65d..4ca71000e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ sphinx==3.1.0 recommonmark==0.6.0 sphinx_rtd_theme==0.4.3 -sphinxcontrib-images==0.9.2 \ No newline at end of file +sphinxcontrib-images==0.9.2 +sphinx-copybutton==0.3.1 \ No newline at end of file diff --git a/docs/rllib/intro/index.rst b/docs/rllib/intro/index.rst new file mode 100644 index 000000000..c94398ce6 --- /dev/null +++ b/docs/rllib/intro/index.rst @@ -0,0 +1,153 @@ +.. _doc_rllib_intro: + +################################# +Reinforcement Learning with RLLib +################################# + + +Griddly provides support for reinforcement learning using the `RLLib `_ reinforcement learning library. + +While RLLib doesn't support OpenAI Gym registered environments, it does provide a similar interface which is supported by Griddly's ``RLLibEnv`` environment. + +Griddly provides two classes, ``RLLibEnv`` and ``RLLibMultiAgentWrapper`` which abstract away all the tedious parts of wrapping environments for RL and leaves you to concentrate on training algorithms, designing networks and game mechanics. + + +********************** +Environment Parameters +********************** + +Parameters for the environments, such as the :ref:`GDY ` file for the game and :ref:`Observer options ` can be sent to the environment using the ``env_config`` dictionary. + +Most of the parameters here are the same as the parameters that can be given to the ``gym.make()`` command when creating a :ref:`Griddly environment for OpenAI Gym `. + +.. code-block:: python + + 'env_config': { + 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', + + 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'record_video_config': { + 'frequency': 100000 + }, + + 'random_level_on_reset': True, + 'max_steps': 1000, + }, + + + +The above example will also record a video of the environment (rendered using the ``SPRITE_2D`` renderer) for one episode every 100000 steps. +Finally the max_steps of the environment will be override to be 1000 steps before the environment is reset automatically. + +******************* +Level Randomization +******************* + +Partially observable games have a fixed observations space regardless of the size of the levels. Additionally several games have levels of fixed size. + +With these games, the level can be randomized at the end of every episode using the ``random_level_on_reset`` option in the ``env_config`` section of RLLib's config. + +.. code-block:: python + + 'env_config': { + + 'random_level_on_reset': True, + + ... + +If this is set to true then the agent will be placed in one of the random levels described in the GDY file each time the episode restarts. + + +********************** +Global Average Pooling +********************** + +Griddly environments' observation spaces differ between games, levels and visualization options. In order to handle this in a generic way using neural networks, we provide a Global Average Pooling agent `GAPAgent`, which can be used with any 2D environment with no additional configuration. + +All you need to do is register the custom model with RLLib and then use it in your training ``config``: + +.. code-block:: python + + ModelCatalog.register_custom_model('GAP', GAPAgent) + + ... + + config = { + + 'model': { + 'custom_model': 'GAP' + 'custom_model_config': ..... + } + + ... + + } + + + +The implementation of the Global Average Pooling agent is essentially a stack of convolutions that maintain the shape of the state, + +.. code-block:: python + + class GAPAgent(TorchModelV2, nn.Module): + """ + Global Average Pooling Agent + This is the same agent used in https://arxiv.org/abs/2011.06363. + + Global average pooling is a simple way to allow training grid-world environments regardless o the size of the grid. + """ + + def __init__(self, obs_space, action_space, num_outputs, model_config, name): + super().__init__(obs_space, action_space, num_outputs, model_config, name) + nn.Module.__init__(self) + + self._num_objects = obs_space.shape[2] + self._num_actions = num_outputs + + self.network = nn.Sequential( + layer_init(nn.Conv2d(self._num_objects, 32, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(32, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + GlobalAvePool(2048), + layer_init(nn.Linear(2048, 1024)), + nn.ReLU(), + layer_init(nn.Linear(1024, 512)), + nn.ReLU(), + layer_init(nn.Linear(512, 512)) + ) + + self._actor_head = nn.Sequential( + layer_init(nn.Linear(512, 512), std=0.01), + nn.ReLU(), + layer_init(nn.Linear(512, self._num_actions), std=0.01) + ) + + self._critic_head = nn.Sequential( + layer_init(nn.Linear(512, 1), std=0.01) + ) + + def forward(self, input_dict, state, seq_lens): + obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) + network_output = self.network(obs_transformed) + value = self._critic_head(network_output) + self._value = value.reshape(-1) + logits = self._actor_head(network_output) + return logits, state + + def value_function(self): + return self._value + + +.. seealso:: You can read more about agents that use Global Average Pooling here: https://arxiv.org/abs/2005.11247 + + +**************** +Recording Videos +**************** + +Videos are recorded of the global observer \ No newline at end of file diff --git a/docs/rllib/multi-agent/index.rst b/docs/rllib/multi-agent/index.rst new file mode 100644 index 000000000..56695c292 --- /dev/null +++ b/docs/rllib/multi-agent/index.rst @@ -0,0 +1,18 @@ +.. _doc_rllib_multi_agent: + +########### +Multi Agent +########### + +Griddly automatically wraps multi-agent games for compatibility with RLLib using the `RLLibMultiAgentWrapper`. + +To register the multi-agent Griddly environment for usage with RLLib, the environment can be wrapped in the following way: + +.. code-block:: python + + # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play + def _create_env(env_config): + env = RLlibEnv(env_config) + return RLlibMultiAgentWrapper(env, env_config) + + register_env(env_name, _create_env) diff --git a/docs/rllib/rts/index.rst b/docs/rllib/rts/index.rst new file mode 100644 index 000000000..620783ab5 --- /dev/null +++ b/docs/rllib/rts/index.rst @@ -0,0 +1,7 @@ +.. _doc_rllib_rts: + +################## +Real Time Strategy +################## + +Coming Soon! \ No newline at end of file diff --git a/docs/rllib/single-agent/index.rst b/docs/rllib/single-agent/index.rst new file mode 100644 index 000000000..00c28e271 --- /dev/null +++ b/docs/rllib/single-agent/index.rst @@ -0,0 +1,81 @@ +.. _doc_rllib_single_agent: + +################### +Single Agent Games +################### + +The Griddly RLLibEnv wrapper allows any of the single-agent games to be trained with many of the single agent RLLib Algorithms. + +.. code-block:: python + + register_env('my-single-agent-environment', RLlibEnv) + + +The example below uses IMPALA to train on the :ref:`Partially Observable Clusters ` Environment + + +************ +Full Example +************ + +.. code-block:: python + + import os + import sys + + import ray + from ray import tune + from ray.rllib.agents.impala import ImpalaTrainer + from ray.rllib.models import ModelCatalog + from ray.tune.registry import register_env + + from griddly import gd + from griddly.util.rllib.torch import GAPAgent + from griddly.util.rllib.wrappers.core import RLlibEnv + + if __name__ == '__main__': + + ray.init(num_gpus=1) + + env_name = "ray-griddly-env" + + register_env(env_name, RLlibEnv) + ModelCatalog.register_custom_model("GAP", GAPAgent) + + max_training_steps = 100000000 + + config = { + 'framework': 'torch', + 'num_workers': 8, + 'num_envs_per_worker': 4, + + 'model': { + 'custom_model': 'GAP', + 'custom_model_config': {} + }, + 'env': env_name, + 'env_config': { + 'record_video_config': { + 'frequency': 100000 + }, + + 'random_level_on_reset': True, + 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', + 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'max_steps': 1000, + }, + 'entropy_coeff_schedule': [ + [0, 0.01], + [max_training_steps, 0.0] + ], + 'lr_schedule': [ + [0, 0.0005], + [max_training_steps, 0.0] + ] + } + + stop = { + "timesteps_total": max_training_steps, + } + + result = tune.run(ImpalaTrainer, config=config, stop=stop) From fab297705d82437e6f3753e2cf3dbc4d6d4d056e Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 7 Mar 2021 14:25:52 +0000 Subject: [PATCH 26/34] more docs and more small fixes --- docs/games/Bait/index.rst | 2 +- docs/games/Bait_With_Keys/index.rst | 2 +- docs/games/Butterflies_and_Spiders/index.rst | 2 +- docs/games/Clusters/index.rst | 2 +- docs/games/Cook_Me_Pasta/index.rst | 2 +- docs/games/Doggo/index.rst | 2 +- docs/games/Drunk_Dwarf/index.rst | 2 +- docs/games/Eyeball/index.rst | 2 +- docs/games/GriddlyRTS/index.rst | 2 +- docs/games/Heal_Or_Die/index.rst | 2 +- docs/games/Kill_The_King/index.rst | 2 +- docs/games/Labyrinth/index.rst | 2 +- .../games/Partially_Observable_Bait/index.rst | 2 +- .../Partially_Observable_Clusters/index.rst | 2 +- .../index.rst | 2 +- .../Partially_Observable_Labyrinth/index.rst | 2 +- .../index.rst | 2 +- .../Partially_Observable_Zelda/index.rst | 2 +- .../Partially_Observable_Zen_Puzzle/index.rst | 2 +- docs/games/Push_Mania/index.rst | 2 +- docs/games/Random_butterflies/index.rst | 2 +- docs/games/Robot_Tag_12v12/index.rst | 2 +- docs/games/Robot_Tag_4v4/index.rst | 2 +- docs/games/Robot_Tag_8v8/index.rst | 2 +- docs/games/Sokoban/index.rst | 2 +- docs/games/Sokoban_-_2/index.rst | 2 +- docs/games/Spider_Nest/index.rst | 2 +- docs/games/Spiders/index.rst | 2 +- docs/games/Zelda/index.rst | 2 +- docs/games/Zelda_Sequential/index.rst | 2 +- docs/games/Zen_Puzzle/index.rst | 2 +- .../observation spaces/index.rst | 28 +- docs/rllib/intro/index.rst | 24 +- docs/rllib/multi-agent/index.rst | 4 + ...y_Observable_Clusters-level-Sprite2D-1.png | Bin 0 -> 3752 bytes docs/rllib/single-agent/index.rst | 12 +- python/examples/griddlyrts/predator_prey.yaml | 262 ++++++++++++++++++ python/examples/rllib/forage.yaml | 254 +++++++++++++++++ python/examples/rllib/predator_prey.yaml | 0 .../griddly/util/rllib/torch/agents/common.py | 0 .../util/rllib/torch/agents/conv_agent.py | 0 41 files changed, 610 insertions(+), 36 deletions(-) create mode 100644 docs/rllib/single-agent/img/Partially_Observable_Clusters-level-Sprite2D-1.png create mode 100644 python/examples/griddlyrts/predator_prey.yaml create mode 100644 python/examples/rllib/forage.yaml create mode 100644 python/examples/rllib/predator_prey.yaml create mode 100644 python/griddly/util/rllib/torch/agents/common.py create mode 100644 python/griddly/util/rllib/torch/agents/conv_agent.py diff --git a/docs/games/Bait/index.rst b/docs/games/Bait/index.rst index 0e037d355..a619758ef 100644 --- a/docs/games/Bait/index.rst +++ b/docs/games/Bait/index.rst @@ -1,4 +1,4 @@ -.. _doc_bait +.. _doc_bait: Bait ==== diff --git a/docs/games/Bait_With_Keys/index.rst b/docs/games/Bait_With_Keys/index.rst index dbb7d7333..83775b371 100644 --- a/docs/games/Bait_With_Keys/index.rst +++ b/docs/games/Bait_With_Keys/index.rst @@ -1,4 +1,4 @@ -.. _doc_bait_with_keys +.. _doc_bait_with_keys: Bait With Keys ============== diff --git a/docs/games/Butterflies_and_Spiders/index.rst b/docs/games/Butterflies_and_Spiders/index.rst index da2a51b25..9c3255d2c 100644 --- a/docs/games/Butterflies_and_Spiders/index.rst +++ b/docs/games/Butterflies_and_Spiders/index.rst @@ -1,4 +1,4 @@ -.. _doc_butterflies_and_spiders +.. _doc_butterflies_and_spiders: Butterflies and Spiders ======================= diff --git a/docs/games/Clusters/index.rst b/docs/games/Clusters/index.rst index 61f4d6a62..68be1cd6c 100644 --- a/docs/games/Clusters/index.rst +++ b/docs/games/Clusters/index.rst @@ -1,4 +1,4 @@ -.. _doc_clusters +.. _doc_clusters: Clusters ======== diff --git a/docs/games/Cook_Me_Pasta/index.rst b/docs/games/Cook_Me_Pasta/index.rst index f10fe2801..f4fe74597 100644 --- a/docs/games/Cook_Me_Pasta/index.rst +++ b/docs/games/Cook_Me_Pasta/index.rst @@ -1,4 +1,4 @@ -.. _doc_cook_me_pasta +.. _doc_cook_me_pasta: Cook Me Pasta ============= diff --git a/docs/games/Doggo/index.rst b/docs/games/Doggo/index.rst index db8e77df2..f4ad4831a 100644 --- a/docs/games/Doggo/index.rst +++ b/docs/games/Doggo/index.rst @@ -1,4 +1,4 @@ -.. _doc_doggo +.. _doc_doggo: Doggo ===== diff --git a/docs/games/Drunk_Dwarf/index.rst b/docs/games/Drunk_Dwarf/index.rst index 3626aed28..f072a8f1c 100644 --- a/docs/games/Drunk_Dwarf/index.rst +++ b/docs/games/Drunk_Dwarf/index.rst @@ -1,4 +1,4 @@ -.. _doc_drunk_dwarf +.. _doc_drunk_dwarf: Drunk Dwarf =========== diff --git a/docs/games/Eyeball/index.rst b/docs/games/Eyeball/index.rst index 82ab85e12..1329295c9 100644 --- a/docs/games/Eyeball/index.rst +++ b/docs/games/Eyeball/index.rst @@ -1,4 +1,4 @@ -.. _doc_eyeball +.. _doc_eyeball: Eyeball ======= diff --git a/docs/games/GriddlyRTS/index.rst b/docs/games/GriddlyRTS/index.rst index 4520e1cd8..50bcf19d1 100644 --- a/docs/games/GriddlyRTS/index.rst +++ b/docs/games/GriddlyRTS/index.rst @@ -1,4 +1,4 @@ -.. _doc_griddlyrts +.. _doc_griddlyrts: GriddlyRTS ========== diff --git a/docs/games/Heal_Or_Die/index.rst b/docs/games/Heal_Or_Die/index.rst index 045e3ec43..9c984bf7c 100644 --- a/docs/games/Heal_Or_Die/index.rst +++ b/docs/games/Heal_Or_Die/index.rst @@ -1,4 +1,4 @@ -.. _doc_heal_or_die +.. _doc_heal_or_die: Heal Or Die =========== diff --git a/docs/games/Kill_The_King/index.rst b/docs/games/Kill_The_King/index.rst index 87f9e1a8e..a93953767 100644 --- a/docs/games/Kill_The_King/index.rst +++ b/docs/games/Kill_The_King/index.rst @@ -1,4 +1,4 @@ -.. _doc_kill_the_king +.. _doc_kill_the_king: Kill The King ============= diff --git a/docs/games/Labyrinth/index.rst b/docs/games/Labyrinth/index.rst index 5a89a3ec4..43ffda062 100644 --- a/docs/games/Labyrinth/index.rst +++ b/docs/games/Labyrinth/index.rst @@ -1,4 +1,4 @@ -.. _doc_labyrinth +.. _doc_labyrinth: Labyrinth ========= diff --git a/docs/games/Partially_Observable_Bait/index.rst b/docs/games/Partially_Observable_Bait/index.rst index 8c4de2c2f..eeae199fb 100644 --- a/docs/games/Partially_Observable_Bait/index.rst +++ b/docs/games/Partially_Observable_Bait/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_bait +.. _doc_partially_observable_bait: Partially Observable Bait ========================= diff --git a/docs/games/Partially_Observable_Clusters/index.rst b/docs/games/Partially_Observable_Clusters/index.rst index f62e1fad2..a110eadcd 100644 --- a/docs/games/Partially_Observable_Clusters/index.rst +++ b/docs/games/Partially_Observable_Clusters/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_clusters +.. _doc_partially_observable_clusters: Partially Observable Clusters ============================= diff --git a/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst b/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst index a4d6a69a4..e6e4f24af 100644 --- a/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst +++ b/docs/games/Partially_Observable_Cook_Me_Pasta/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_cook_me_pasta +.. _doc_partially_observable_cook_me_pasta: Partially Observable Cook Me Pasta ================================== diff --git a/docs/games/Partially_Observable_Labyrinth/index.rst b/docs/games/Partially_Observable_Labyrinth/index.rst index f1fbcd8ca..7ab4f5a17 100644 --- a/docs/games/Partially_Observable_Labyrinth/index.rst +++ b/docs/games/Partially_Observable_Labyrinth/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_labyrinth +.. _doc_partially_observable_labyrinth: Partially Observable Labyrinth ============================== diff --git a/docs/games/Partially_Observable_Sokoban_-_2/index.rst b/docs/games/Partially_Observable_Sokoban_-_2/index.rst index 267e3f9a0..12f6459d5 100644 --- a/docs/games/Partially_Observable_Sokoban_-_2/index.rst +++ b/docs/games/Partially_Observable_Sokoban_-_2/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_sokoban_-_2 +.. _doc_partially_observable_sokoban_-_2: Partially Observable Sokoban - 2 ================================ diff --git a/docs/games/Partially_Observable_Zelda/index.rst b/docs/games/Partially_Observable_Zelda/index.rst index a50008be2..b35b0ad93 100644 --- a/docs/games/Partially_Observable_Zelda/index.rst +++ b/docs/games/Partially_Observable_Zelda/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_zelda +.. _doc_partially_observable_zelda: Partially Observable Zelda ========================== diff --git a/docs/games/Partially_Observable_Zen_Puzzle/index.rst b/docs/games/Partially_Observable_Zen_Puzzle/index.rst index 474a5b7e5..d9c7e561e 100644 --- a/docs/games/Partially_Observable_Zen_Puzzle/index.rst +++ b/docs/games/Partially_Observable_Zen_Puzzle/index.rst @@ -1,4 +1,4 @@ -.. _doc_partially_observable_zen_puzzle +.. _doc_partially_observable_zen_puzzle: Partially Observable Zen Puzzle =============================== diff --git a/docs/games/Push_Mania/index.rst b/docs/games/Push_Mania/index.rst index 0442d2e1e..69e892f14 100644 --- a/docs/games/Push_Mania/index.rst +++ b/docs/games/Push_Mania/index.rst @@ -1,4 +1,4 @@ -.. _doc_push_mania +.. _doc_push_mania: Push Mania ========== diff --git a/docs/games/Random_butterflies/index.rst b/docs/games/Random_butterflies/index.rst index 4b48a64a1..8f2d91b12 100644 --- a/docs/games/Random_butterflies/index.rst +++ b/docs/games/Random_butterflies/index.rst @@ -1,4 +1,4 @@ -.. _doc_random_butterflies +.. _doc_random_butterflies: Random butterflies ================== diff --git a/docs/games/Robot_Tag_12v12/index.rst b/docs/games/Robot_Tag_12v12/index.rst index 7a91fd446..10839d340 100644 --- a/docs/games/Robot_Tag_12v12/index.rst +++ b/docs/games/Robot_Tag_12v12/index.rst @@ -1,4 +1,4 @@ -.. _doc_robot_tag_12v12 +.. _doc_robot_tag_12v12: Robot Tag 12v12 =============== diff --git a/docs/games/Robot_Tag_4v4/index.rst b/docs/games/Robot_Tag_4v4/index.rst index c3b80a8b3..83be60f15 100644 --- a/docs/games/Robot_Tag_4v4/index.rst +++ b/docs/games/Robot_Tag_4v4/index.rst @@ -1,4 +1,4 @@ -.. _doc_robot_tag_4v4 +.. _doc_robot_tag_4v4: Robot Tag 4v4 ============= diff --git a/docs/games/Robot_Tag_8v8/index.rst b/docs/games/Robot_Tag_8v8/index.rst index 35216fa42..6350d8873 100644 --- a/docs/games/Robot_Tag_8v8/index.rst +++ b/docs/games/Robot_Tag_8v8/index.rst @@ -1,4 +1,4 @@ -.. _doc_robot_tag_8v8 +.. _doc_robot_tag_8v8: Robot Tag 8v8 ============= diff --git a/docs/games/Sokoban/index.rst b/docs/games/Sokoban/index.rst index 73a6aeeff..f93afc905 100644 --- a/docs/games/Sokoban/index.rst +++ b/docs/games/Sokoban/index.rst @@ -1,4 +1,4 @@ -.. _doc_sokoban +.. _doc_sokoban: Sokoban ======= diff --git a/docs/games/Sokoban_-_2/index.rst b/docs/games/Sokoban_-_2/index.rst index 981af25ce..a48ed0803 100644 --- a/docs/games/Sokoban_-_2/index.rst +++ b/docs/games/Sokoban_-_2/index.rst @@ -1,4 +1,4 @@ -.. _doc_sokoban_-_2 +.. _doc_sokoban_-_2: Sokoban - 2 =========== diff --git a/docs/games/Spider_Nest/index.rst b/docs/games/Spider_Nest/index.rst index 0c3ba28b9..1f94722b5 100644 --- a/docs/games/Spider_Nest/index.rst +++ b/docs/games/Spider_Nest/index.rst @@ -1,4 +1,4 @@ -.. _doc_spider_nest +.. _doc_spider_nest: Spider Nest =========== diff --git a/docs/games/Spiders/index.rst b/docs/games/Spiders/index.rst index ca7aafdd8..509c51ebd 100644 --- a/docs/games/Spiders/index.rst +++ b/docs/games/Spiders/index.rst @@ -1,4 +1,4 @@ -.. _doc_spiders +.. _doc_spiders: Spiders ======= diff --git a/docs/games/Zelda/index.rst b/docs/games/Zelda/index.rst index e61a4b084..93d34b839 100644 --- a/docs/games/Zelda/index.rst +++ b/docs/games/Zelda/index.rst @@ -1,4 +1,4 @@ -.. _doc_zelda +.. _doc_zelda: Zelda ===== diff --git a/docs/games/Zelda_Sequential/index.rst b/docs/games/Zelda_Sequential/index.rst index a38c172f4..2d93d968e 100644 --- a/docs/games/Zelda_Sequential/index.rst +++ b/docs/games/Zelda_Sequential/index.rst @@ -1,4 +1,4 @@ -.. _doc_zelda_sequential +.. _doc_zelda_sequential: Zelda Sequential ================ diff --git a/docs/games/Zen_Puzzle/index.rst b/docs/games/Zen_Puzzle/index.rst index 0d8ff6f7e..bde14ffdf 100644 --- a/docs/games/Zen_Puzzle/index.rst +++ b/docs/games/Zen_Puzzle/index.rst @@ -1,4 +1,4 @@ -.. _doc_zen_puzzle +.. _doc_zen_puzzle: Zen Puzzle ========== diff --git a/docs/getting-started/observation spaces/index.rst b/docs/getting-started/observation spaces/index.rst index 349bf9a7a..c2f3802f6 100644 --- a/docs/getting-started/observation spaces/index.rst +++ b/docs/getting-started/observation spaces/index.rst @@ -116,15 +116,37 @@ The options for both the ``player_observer_type`` and ``global_observer_type`` a .. image:: img/Spiders-level-Isometric-2.png + +.. _vector_observer: + ****** Vector ****** -Vector observers will return a tensor of shape [*objects*, *width*, *height*] where each value is either 0 or 1 denoting that there is an object of that type in a particular location. +Vector observers will return a tensor of shape [*objects, player ids, object_rotation, variables*, *width*, *height*] where each value is either 0 or 1 denoting that there is an object of that type in a particular location. + +The data contained in the cell can be configured using the vector options in the :ref:`GDY observer configuration <#/properties/Environment/properties/Observers/properties/Vector>`. + +:Objects: + +Each cell always contains a multi-label representation of whether an object is present (1) in that cell or not (0). + +The order of the object index in each [x,y] location can be retrieved by calling ``env.game.get_object_names()``. + +:IncludePlayerId: + If this option is set, each cell of the observation tensor also contains a one-hot representation of which player an object belongs to. + +.. warning:: In multi-agent scenarios, every agent sees themselves as player 1. + +:IncludeRotation: + This option appends a one-hot to the cell representing the rotation of the object at that position. + +:IncludeVariables: + If set, the local variables of each object are provided. The order of the variables can be retrieved by calling ``env.game.get_object_variable_names()`` + -The order of the object index in each [x,y] location can be retrieved by calling ``env.gdy.get_object_names()``. -As an example in an 5x5 environment that has three types of object: `avatar`, `wall` and `goal`: +As an example, in an 5x5 environment that has three types of object: `avatar`, `wall` and `goal` and no other options are set: .. code-block:: python diff --git a/docs/rllib/intro/index.rst b/docs/rllib/intro/index.rst index c94398ce6..3ec106dff 100644 --- a/docs/rllib/intro/index.rst +++ b/docs/rllib/intro/index.rst @@ -11,6 +11,8 @@ While RLLib doesn't support OpenAI Gym registered environments, it does provide Griddly provides two classes, ``RLLibEnv`` and ``RLLibMultiAgentWrapper`` which abstract away all the tedious parts of wrapping environments for RL and leaves you to concentrate on training algorithms, designing networks and game mechanics. +Examples for :ref:`single-agent ` and :ref:`multi-agent ` training are provided. + ********************** Environment Parameters @@ -57,6 +59,7 @@ With these games, the level can be randomized at the end of every episode using If this is set to true then the agent will be placed in one of the random levels described in the GDY file each time the episode restarts. +.. _gap_agent: ********************** Global Average Pooling @@ -150,4 +153,23 @@ The implementation of the Global Average Pooling agent is essentially a stack of Recording Videos **************** -Videos are recorded of the global observer \ No newline at end of file +Griddly can automatically record videos during training by placing the ``record_video_config`` dictionary into the standard RLLib ``env_config``. + +.. code-block:: python + + 'env_config': + 'record_video_config': { + 'frequency': 20000 + }, + + ... + } + +Videos are recorded using the global observer. This allows multi agent environments to be viewed from the perspective of the whole game rather than the individual observations of the agents. + +The triggering of videos is configured using the ``frequency`` variable. The ``frequency`` variable refers to the number of steps in each environment that pass before the recording is triggered. + +Once triggered, the next episode is recorded in full. Videos of episodes are recorded on every ray environment. + + +.. seealso:: For more information on how to configure observers see :ref:`Observation Spaces ` \ No newline at end of file diff --git a/docs/rllib/multi-agent/index.rst b/docs/rllib/multi-agent/index.rst index 56695c292..714593331 100644 --- a/docs/rllib/multi-agent/index.rst +++ b/docs/rllib/multi-agent/index.rst @@ -16,3 +16,7 @@ To register the multi-agent Griddly environment for usage with RLLib, the enviro return RLlibMultiAgentWrapper(env, env_config) register_env(env_name, _create_env) + +************ +Full Example +************ \ No newline at end of file diff --git a/docs/rllib/single-agent/img/Partially_Observable_Clusters-level-Sprite2D-1.png b/docs/rllib/single-agent/img/Partially_Observable_Clusters-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d18178ddf7b969c0aa6e3e34cf93e3c4850bcd2a GIT binary patch literal 3752 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 QCs5$r8RTiosmt;I4-K-eN&o-= literal 0 HcmV?d00001 diff --git a/docs/rllib/single-agent/index.rst b/docs/rllib/single-agent/index.rst index 00c28e271..1125901c3 100644 --- a/docs/rllib/single-agent/index.rst +++ b/docs/rllib/single-agent/index.rst @@ -11,7 +11,17 @@ The Griddly RLLibEnv wrapper allows any of the single-agent games to be trained register_env('my-single-agent-environment', RLlibEnv) -The example below uses IMPALA to train on the :ref:`Partially Observable Clusters ` Environment +The example below uses IMPALA to train on the :ref:`Partially Observable Clusters ` Environment. + +The agent in the :ref:`Partially Observable Clusters ` environment has a 5x5 partially observable ego-centric view. + +By default the agent sees a :ref:`VECTOR ` view of the environment. This view is passed to a :ref:`Global Average Pooling Agent ` to produce the policy. + + +.. figure:: img/Partially_Observable_Clusters-level-Sprite2D-1.png + :align: center + + The Clusters environment as seen from the "Global Observer" view. ************ diff --git a/python/examples/griddlyrts/predator_prey.yaml b/python/examples/griddlyrts/predator_prey.yaml new file mode 100644 index 000000000..d490b8c8b --- /dev/null +++ b/python/examples/griddlyrts/predator_prey.yaml @@ -0,0 +1,262 @@ +Version: "0.1" +Environment: + Name: Predator Prey + Description: + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 4 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f1 . . . . . . . . . . . . . . . f2 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . f3 . . . . . . . . . . . . . . f4 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f1 . m . . . . . . . . . . m . f2 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f3 . m . . . . . . . . . . m . f4 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f1 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f2 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f4 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + Dst: + Object: tagger + Commands: + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + +Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/python/examples/rllib/forage.yaml b/python/examples/rllib/forage.yaml new file mode 100644 index 000000000..3754ad30e --- /dev/null +++ b/python/examples/rllib/forage.yaml @@ -0,0 +1,254 @@ +Version: "0.1" +Environment: + Name: Predator Prey + Description: + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 4 + Observer: + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f1 . . . . . . . . . . . . . . . f2 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . f3 . . . . . . . . . . . . . . f4 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f1 . m . . . . . . . . . . m . f2 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f3 . m . . . . . . . . . . m . f4 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f1 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f2 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f4 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + Dst: + Object: tagger + Commands: + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + +Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/python/examples/rllib/predator_prey.yaml b/python/examples/rllib/predator_prey.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/torch/agents/common.py b/python/griddly/util/rllib/torch/agents/common.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/griddly/util/rllib/torch/agents/conv_agent.py b/python/griddly/util/rllib/torch/agents/conv_agent.py new file mode 100644 index 000000000..e69de29bb From 760e1347097ce41744ba2359d0d9f7a15a463609 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Sun, 7 Mar 2021 20:04:54 +0000 Subject: [PATCH 27/34] need to add these things too --- bindings/python.cpp | 1 + bindings/wrapper/GDYWrapper.cpp | 4 + python/docs/sphinxdocs/game_docs.py | 8 +- .../griddlyrts/griddly_rts_global.png | Bin 905 -> 4058 bytes python/examples/griddlyrts/griddly_rts_p1.png | Bin 905 -> 186 bytes python/examples/griddlyrts/griddly_rts_p2.png | Bin 905 -> 186 bytes .../griddlyrts/play_griddlyrts_gym.py | 6 +- python/examples/rllib/foragers.yaml | 197 +++++++++++++ python/examples/rllib/predator_prey.yaml | 254 +++++++++++++++++ python/examples/rllib/repro/reproduce.py | 35 +++ .../rllib/rllib_multiagent_taggers.py | 23 +- python/examples/rllib/rllib_single_agent.py | 4 +- .../griddly/util/rllib/torch/agents/common.py | 11 + .../util/rllib/torch/agents/conv_agent.py | 57 ++++ .../agents/global_average_pooling_agent.py | 6 +- python/griddly/util/vector_visualization.py | 5 +- resources/games/Multi-Agent/foragers.yaml | 150 ++++++++++ resources/games/Multi-Agent/robot_tag_12.yaml | 266 ++++++++++++++++++ .../games/Multi-Agent/robot_tag_4.yaml | 16 +- resources/games/Multi-Agent/robot_tag_8.yaml | 266 ++++++++++++++++++ src/Griddly/Core/GDY/GDYFactory.cpp | 2 +- src/Griddly/Core/GDY/GDYFactory.hpp | 2 +- tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp | 20 +- 23 files changed, 1296 insertions(+), 37 deletions(-) create mode 100644 python/examples/rllib/foragers.yaml create mode 100644 python/examples/rllib/repro/reproduce.py create mode 100644 resources/games/Multi-Agent/foragers.yaml create mode 100644 resources/games/Multi-Agent/robot_tag_12.yaml rename python/examples/rllib/forage.yaml => resources/games/Multi-Agent/robot_tag_4.yaml (97%) create mode 100644 resources/games/Multi-Agent/robot_tag_8.yaml diff --git a/bindings/python.cpp b/bindings/python.cpp index dfbc8917f..49f05746f 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -34,6 +34,7 @@ PYBIND11_MODULE(python_griddly, m) { gdy.def("get_action_input_mappings", &Py_GDYWrapper::getActionInputMappings); gdy.def("get_avatar_object", &Py_GDYWrapper::getAvatarObject); gdy.def("create_game", &Py_GDYWrapper::createGame); + gdy.def("get_level_count", &Py_GDYWrapper::getLevelCount); py::class_> game_process(m, "GameProcess"); diff --git a/bindings/wrapper/GDYWrapper.cpp b/bindings/wrapper/GDYWrapper.cpp index 2be9fc232..83c15a860 100644 --- a/bindings/wrapper/GDYWrapper.cpp +++ b/bindings/wrapper/GDYWrapper.cpp @@ -35,6 +35,10 @@ class Py_GDYWrapper { return gdyFactory_->getExternalActionNames(); } + uint32_t getLevelCount() const { + return gdyFactory_->getLevelCount(); + } + py::dict getActionInputMappings() const { auto actionInputsDefinitions = gdyFactory_->getActionInputsDefinitions(); py::dict py_actionInputsDefinitions; diff --git a/python/docs/sphinxdocs/game_docs.py b/python/docs/sphinxdocs/game_docs.py index 03d7e919e..ab10a984a 100644 --- a/python/docs/sphinxdocs/game_docs.py +++ b/python/docs/sphinxdocs/game_docs.py @@ -187,11 +187,17 @@ def _generate_game_doc(self, game_breakdown, relative_gdy_path): description = game_breakdown.description name = game_breakdown.name + filename = relative_gdy_path images = {} - sphinx_string = name + '\n' + + sphinx_string = f'.. _doc_{name.lower().replace(" ", "_")}:\n\n' + + sphinx_string += name + '\n' sphinx_string += '=' * len(name) + '\n\n' + sphinx_string +=f'.. code-block::\n\n {filename}\n\n' + sphinx_string += 'Description\n' sphinx_string += '-------------\n\n' diff --git a/python/examples/griddlyrts/griddly_rts_global.png b/python/examples/griddlyrts/griddly_rts_global.png index 17f72846a5440c1217c0c7dd164e6bbdd3d652c7..aa190b14cd6102db8c84ac5dd56cc847ccee451b 100644 GIT binary patch literal 4058 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 I*Sj12KQJ=nnE(I) literal 905 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_tH;PZ!6Kid%2*dgo~wins>K zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf diff --git a/python/examples/griddlyrts/griddly_rts_p1.png b/python/examples/griddlyrts/griddly_rts_p1.png index 17f72846a5440c1217c0c7dd164e6bbdd3d652c7..8bc6b1cddddb8014eb9f02a10006d607b8fbc1ef 100644 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901SGegx)utg8a-VcLn>~)yK zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf diff --git a/python/examples/griddlyrts/griddly_rts_p2.png b/python/examples/griddlyrts/griddly_rts_p2.png index 17f72846a5440c1217c0c7dd164e6bbdd3d652c7..8bc6b1cddddb8014eb9f02a10006d607b8fbc1ef 100644 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901SGegx)utg8a-VcLn>~)yK zRK|5ooGx*5=~Oo_;o;mGmw=WN-TtM!Pq+_pGW1w0P_DLvJv&^5Qr1oc}Ql#2W6sLlmeL!B!c((TY ziZ_`tP7W=oHtta9o+%t<=elf<@66AiGqE_>Tg+JAI7>LW`>_y{5{n=TULJ8~&V^61 zZj1N*Kk;1;(`MvA1F;pH`Waj}lvrqwFQ1Y-_$uhgg#YG2pFYH*1+s!u#KfaKQ=_I? zT{F`|iMI{`7Y?2j!_5t!%$BCsUix%yvEA)ssu=E-iaevU{(jL>HJ}`7h$yw_0_7Im zUw-1uPvhMfQQ;5{jEc=Mg`A9nO&s{JV~ZRk$T65$@9=gb!?`8>hgN^Q^R{{!C^aBb z0*L*?s9RF&>+P*?ws#g{ravt)#^CAd=d#Wzp$P!#vI}bf diff --git a/python/examples/griddlyrts/play_griddlyrts_gym.py b/python/examples/griddlyrts/play_griddlyrts_gym.py index 06cb7344c..8159740f2 100644 --- a/python/examples/griddlyrts/play_griddlyrts_gym.py +++ b/python/examples/griddlyrts/play_griddlyrts_gym.py @@ -10,8 +10,8 @@ wrapper = GymWrapperFactory() wrapper.build_gym_from_yaml("GriddlyRTS-Adv", - 'RTS/GriddlyRTS.yaml', - global_observer_type=gd.ObserverType.VECTOR, + 'predator_prey.yaml', + global_observer_type=gd.ObserverType.SPRITE_2D, player_observer_type=gd.ObserverType.VECTOR, level=0) @@ -59,7 +59,7 @@ for x, y in zip(valid_locations[0], valid_locations[1]): action_names = env.gdy.get_action_names() action_mask = env.get_unit_action_mask([x,y], action_names) - print(action_mask) + #print(action_mask) action_masks = env.get_unit_action_mask([6, 3], ['gather', 'move'], padded=False) diff --git a/python/examples/rllib/foragers.yaml b/python/examples/rllib/foragers.yaml new file mode 100644 index 000000000..ed442b423 --- /dev/null +++ b/python/examples/rllib/foragers.yaml @@ -0,0 +1,197 @@ +Version: "0.1" +Environment: + Name: Foragers + Description: + Observers: + Sprite2D: + TileSize: 24 + BackgroundTile: gvgai/oryx/grass_15.png + Block2D: + TileSize: 24 + Player: + Count: 4 + Observer: + TrackAvatar: true + Height: 5 + Width: 5 + OffsetX: 0 + OffsetY: 0 + AvatarObject: harvester + Variables: + - Name: potion_count + InitialValue: 0 + Termination: + End: + - eq: [potion_count, 0] + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . r b r . . W + W . . r g r . . W + W . . r g r . . W + W . . r b r . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . W W . W W . W + W . W r b r W . W + W . . r g r . . W + W . W r g r W . W + W . W r b r W . W + W . W W . W W . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W + W f1 . . . . . . W W . . . . . . f2 W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W W W W W W W W W W W W W W W W W W + W W W W W W W W W W W W W W W W W W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W f4 . . . . . . W W . . . . . . f3 W + W W W W W W W W W W W W W W W W W W + + +Actions: + - Name: init_potion + InputMapping: + Internal: true + Inputs: + 1: + Description: "The only action here is to increment the potion count" + Behaviours: + - Src: + Object: [ potion1, potion2, potion3 ] + Commands: + - incr: potion_count + Dst: + Object: [ potion1, potion2, potion3 ] + + - Name: gather + Behaviours: + - Src: + Object: harvester + Commands: + - reward: 1 + Dst: + Object: [potion1, potion2, potion3] + Commands: + - decr: value + - eq: + Arguments: [ value, 0 ] + Commands: + - decr: potion_count + - remove: true + + - Name: move + Behaviours: + - Src: + Object: harvester + Commands: + - mov: _dest + Dst: + Object: _empty + +Objects: + - Name: harvester + MapCharacter: f + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/man1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 1.0 + + - Name: potion1 + MapCharacter: b + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 5 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-0.png + Scale: 0.5 + Block2D: + - Color: [ 0.0, 0.0, 0.8 ] + Shape: square + + - Name: potion2 + MapCharacter: r + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-2.png + Scale: 0.8 + Block2D: + - Color: [ 0.8, 0.0, 0.0 ] + Shape: square + + - Name: potion3 + MapCharacter: g + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 20 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-3.png + Scale: 1.0 + Block2D: + - Color: [ 0.0, 0.8, 0.0 ] + Shape: square + Scale: 0.8 + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall9-0.png + - oryx/oryx_fantasy/wall9-1.png + - oryx/oryx_fantasy/wall9-2.png + - oryx/oryx_fantasy/wall9-3.png + - oryx/oryx_fantasy/wall9-4.png + - oryx/oryx_fantasy/wall9-5.png + - oryx/oryx_fantasy/wall9-6.png + - oryx/oryx_fantasy/wall9-7.png + - oryx/oryx_fantasy/wall9-8.png + - oryx/oryx_fantasy/wall9-9.png + - oryx/oryx_fantasy/wall9-10.png + - oryx/oryx_fantasy/wall9-11.png + - oryx/oryx_fantasy/wall9-12.png + - oryx/oryx_fantasy/wall9-13.png + - oryx/oryx_fantasy/wall9-14.png + - oryx/oryx_fantasy/wall9-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/python/examples/rllib/predator_prey.yaml b/python/examples/rllib/predator_prey.yaml index e69de29bb..3754ad30e 100644 --- a/python/examples/rllib/predator_prey.yaml +++ b/python/examples/rllib/predator_prey.yaml @@ -0,0 +1,254 @@ +Version: "0.1" +Environment: + Name: Predator Prey + Description: + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 4 + Observer: + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f1 . . . . . . . . . . . . . . . f2 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . f3 . . . . . . . . . . . . . . f4 . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f1 . m . . . . . . . . . . m . f2 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . f3 . m . . . . . . . . . . m . f4 . . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f1 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f2 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f4 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + Dst: + Object: tagger + Commands: + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + +Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/python/examples/rllib/repro/reproduce.py b/python/examples/rllib/repro/reproduce.py new file mode 100644 index 000000000..a4050e000 --- /dev/null +++ b/python/examples/rllib/repro/reproduce.py @@ -0,0 +1,35 @@ + +import ray +from ray import tune +from ray.rllib.agents.ppo import PPOTrainer +from ray.rllib.examples.env.random_env import RandomEnv + +if __name__ == '__main__': + + ray.init(num_gpus=1) + + env_name = 'ray-ma-env' + + config = { + 'framework': 'torch', + 'num_workers': 10, + #'num_envs_per_worker': 1, + + #'use_gae': False, + + 'num_gpus': 1, + + 'env': RandomEnv, + + } + + stop = { + 'timesteps_total': 10000, + } + + trainer = PPOTrainer(config=config) + + for i in range(100): + trainer.train() + + #result = tune.run(PPOTrainer, config=config, stop=stop) \ No newline at end of file diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multiagent_taggers.py index acd8dec66..c3fcb7bce 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multiagent_taggers.py @@ -8,7 +8,7 @@ from ray.tune.registry import register_env from griddly import gd -from griddly.util.rllib.torch import GAPAgent +from griddly.util.rllib.torch.agents.conv_agent import SimpleConvAgent from griddly.util.rllib.wrappers.core import RLlibMultiAgentWrapper, RLlibEnv if __name__ == '__main__': @@ -17,49 +17,50 @@ ray.init(num_gpus=1) - env_name = 'ray-ma-grouped-env' + env_name = 'ray-ma-env' # Create the gridnet environment and wrap it in a multi-agent wrapper for self-play def _create_env(env_config): env = RLlibEnv(env_config) return RLlibMultiAgentWrapper(env, env_config) + register_env(env_name, _create_env) - ModelCatalog.register_custom_model('GAP', GAPAgent) + ModelCatalog.register_custom_model('SimpleConv', SimpleConvAgent) - max_training_steps = 2000000 + max_training_steps = 50000000 config = { 'framework': 'torch', 'num_workers': 8, - 'num_envs_per_worker': 1, + 'num_envs_per_worker': 2, 'model': { - 'custom_model': 'GAP', + 'custom_model': 'SimpleConv', 'custom_model_config': {} }, 'env': env_name, 'env_config': { # in the griddly environment we set a variable to let the training environment # know if that player is no longer active - 'player_done_variable': 'player_done', + # 'player_done_variable': 'player_done', 'record_video_config': { 'frequency': 20000 # number of rollouts }, - 'yaml_file': 'Multi-Agent/robot_tag_4.yaml', + 'random_level_on_reset': True, + 'yaml_file': 'foragers.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, - 'level': 2, - 'max_steps': 1000, + 'max_steps': 500, }, 'entropy_coeff_schedule': [ [0, 0.01], [max_training_steps, 0.0] ], 'lr_schedule': [ - [0, 0.005], + [0, 0.0005], [max_training_steps, 0.0] ] } diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 835f77cba..4cabc969f 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -40,7 +40,7 @@ }, 'random_level_on_reset': True, - 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', + 'yaml_file': 'Single-Player/GVGAI/cookmepasta_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'max_steps': 1000, }, @@ -49,7 +49,7 @@ [max_training_steps, 0.0] ], 'lr_schedule': [ - [0, 0.0005], + [0, 0.005], [max_training_steps, 0.0] ] } diff --git a/python/griddly/util/rllib/torch/agents/common.py b/python/griddly/util/rllib/torch/agents/common.py index e69de29bb..fb9735467 100644 --- a/python/griddly/util/rllib/torch/agents/common.py +++ b/python/griddly/util/rllib/torch/agents/common.py @@ -0,0 +1,11 @@ +import numpy as np +from torch import nn + + +def layer_init(layer, std=np.sqrt(2), bias_const=0.0): + """ + Simple function to init layers + """ + nn.init.orthogonal_(layer.weight, std) + nn.init.constant_(layer.bias, bias_const) + return layer diff --git a/python/griddly/util/rllib/torch/agents/conv_agent.py b/python/griddly/util/rllib/torch/agents/conv_agent.py index e69de29bb..8af68ed33 100644 --- a/python/griddly/util/rllib/torch/agents/conv_agent.py +++ b/python/griddly/util/rllib/torch/agents/conv_agent.py @@ -0,0 +1,57 @@ +import numpy as np +from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 +from torch import nn + +from griddly.util.rllib.torch.agents.common import layer_init + +class SimpleConvAgent(TorchModelV2, nn.Module): + """ + Smiple Convolution agent that calculates the required linear output layer + """ + + def __init__(self, obs_space, action_space, num_outputs, model_config, name): + super().__init__(obs_space, action_space, num_outputs, model_config, name) + nn.Module.__init__(self) + + self._num_objects = obs_space.shape[2] + self._num_actions = num_outputs + + linear_flatten = np.prod(obs_space.shape[:2])*64 + + self.network = nn.Sequential( + layer_init(nn.Conv2d(self._num_objects, 32, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(32, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + nn.Flatten(), + layer_init(nn.Linear(linear_flatten, 1024)), + nn.ReLU(), + layer_init(nn.Linear(1024, 512)), + nn.ReLU(), + layer_init(nn.Linear(512, 512)) + ) + + self._actor_head = nn.Sequential( + layer_init(nn.Linear(512, 512), std=0.01), + nn.ReLU(), + layer_init(nn.Linear(512, self._num_actions), std=0.01) + ) + + self._critic_head = nn.Sequential( + layer_init(nn.Linear(512, 1), std=0.01) + ) + + def forward(self, input_dict, state, seq_lens): + obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) + network_output = self.network(obs_transformed) + value = self._critic_head(network_output) + self._value = value.reshape(-1) + logits = self._actor_head(network_output) + return logits, state + + def value_function(self): + return self._value diff --git a/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py b/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py index 7fcbf67cc..2929508bb 100644 --- a/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py +++ b/python/griddly/util/rllib/torch/agents/global_average_pooling_agent.py @@ -2,11 +2,7 @@ from ray.rllib.models.torch.torch_modelv2 import TorchModelV2 from torch import nn - -def layer_init(layer, std=np.sqrt(2), bias_const=0.0): - nn.init.orthogonal_(layer.weight, std) - nn.init.constant_(layer.bias, bias_const) - return layer +from griddly.util.rllib.torch.agents.common import layer_init class GlobalAvePool(nn.Module): diff --git a/python/griddly/util/vector_visualization.py b/python/griddly/util/vector_visualization.py index 817d3c2d1..21f8dca08 100644 --- a/python/griddly/util/vector_visualization.py +++ b/python/griddly/util/vector_visualization.py @@ -22,8 +22,11 @@ def convert(self, observation): # Add extra dimension so argmax does not get confused by 0 index and empty space palette_buffer = np.ones([self._object_channels + 1, *observation.shape[1:]]) * 0.5 + # only used for debugging + offset = 0 + # Only consider the object type when rendering - palette_buffer[1:] = observation[:self._object_channels, :, :] + palette_buffer[1:] = observation[offset:self._object_channels+offset, :, :] # Convert to RGB pallette vector_pallette = np.argmax(palette_buffer, axis=0).swapaxes(0, 1) diff --git a/resources/games/Multi-Agent/foragers.yaml b/resources/games/Multi-Agent/foragers.yaml new file mode 100644 index 000000000..3c5fd8d5a --- /dev/null +++ b/resources/games/Multi-Agent/foragers.yaml @@ -0,0 +1,150 @@ +Version: "0.1" +Environment: + Name: Foragers + Description: + Observers: + Block2D: + TileSize: 24 + Player: + Count: 4 + Observer: + TrackAvatar: true + Height: 5 + Width: 5 + OffsetX: 0 + OffsetY: 0 + AvatarObject: harvester + Termination: + End: + - eq: [minerals:count, 0] + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . r b r . . W + W . . r g r . . W + W . . r g r . . W + W . . r b r . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . W W . W W . W + W . W r b r W . W + W . . r g r . . W + W . W r g r W . W + W . W r b r W . W + W . W W . W W . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W + W f1 . . . . . . W W . . . . . . f2 W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W W W W W W W W W W W W W W W W W W + W W W W W W W W W W W W W W W W W W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W f4 . . . . . . W W . . . . . . f3 W + W W W W W W W W W W W W W W W W W W + + +Actions: + + - Name: gather + Behaviours: + - Src: + Object: harvester + Commands: + - reward: 1 + Dst: + Object: [potion1, potion2, potion3] + Commands: + - decr: value + - eq: + Arguments: [ value, 0 ] + Commands: + - remove: true + + + - Name: move + Behaviours: + - Src: + Object: harvester + Commands: + - mov: _dest + Dst: + Object: _empty + +Objects: + - Name: harvester + MapCharacter: f + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/man1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 1.0 + + - Name: potion1 + MapCharacter: b + Variables: + - Name: value + InitialValue: 5 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-0.png + Scale: 0.5 + Block2D: + - Color: [ 0.0, 0.0, 0.8 ] + Shape: square + + - Name: potion2 + MapCharacter: r + Variables: + - Name: value + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-2.png + Scale: 0.8 + Block2D: + - Color: [ 0.8, 0.0, 0.0 ] + Shape: square + + - Name: potion3 + MapCharacter: g + Variables: + - Name: value + InitialValue: 20 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-3.png + Scale: 1.0 + Block2D: + - Color: [ 0.0, 0.8, 0.0 ] + Shape: square + Scale: 0.8 + + - Name: fixed_wall + MapCharacter: W + Observers: + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/resources/games/Multi-Agent/robot_tag_12.yaml b/resources/games/Multi-Agent/robot_tag_12.yaml new file mode 100644 index 000000000..eb430ee19 --- /dev/null +++ b/resources/games/Multi-Agent/robot_tag_12.yaml @@ -0,0 +1,266 @@ +Version: "0.1" +Environment: + Name: Robot Tag 8v8 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 12 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W . . f2 . f12 . . W + W . . . . . . . W + W f1 . f3 . f10 . f11 W + W . . . . . . . W + W . . . . . . . W + W f4 . f5 . f7 . f8 W + W . . . . . . . W + W . . f6 . f9 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f12 . . W + W . f1 f3 . . . . . . . . . . . . . . f10 f11 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f4 f5 . . . . . . . . . . . . . . f7 f8 . W + W . . f6 . . . . . . . . . . . . . . f9 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . f2 . m . . . . . . . . . . m . f12 . . W + W . f1 f3 . m . . . . . . . . . . m . f10 f11 . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . f4 f5 . m . . . . . . . . . . m . f7 f8 . W + W . . f6 . m . . . . . . . . . . m . f9 . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f2 . . . . m . . m . . . . . . . . . . m . . m . . . . f12 m . . . . W + W . . f3 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f10 . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . f1 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f11 . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . f4 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f8 . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . f5 . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . f7 . . W + W . . . . m f6 . . . . m . . m . . . . . . . . . . m . . m . . . . f9 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [ tagger, moveable_wall ] + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + +Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/python/examples/rllib/forage.yaml b/resources/games/Multi-Agent/robot_tag_4.yaml similarity index 97% rename from python/examples/rllib/forage.yaml rename to resources/games/Multi-Agent/robot_tag_4.yaml index 3754ad30e..1d8b50eb5 100644 --- a/python/examples/rllib/forage.yaml +++ b/resources/games/Multi-Agent/robot_tag_4.yaml @@ -1,13 +1,16 @@ Version: "0.1" Environment: - Name: Predator Prey - Description: + Name: Robot Tag 4v4 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. Observers: Block2D: TileSize: 24 Sprite2D: TileSize: 24 BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true Variables: - Name: player_done InitialValue: 0 @@ -17,12 +20,16 @@ Environment: Player: Count: 4 Observer: + RotateWithAvatar: true TrackAvatar: true Height: 9 Width: 9 OffsetX: 0 OffsetY: 0 AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] Levels: - | @@ -165,15 +172,20 @@ Actions: - eq: [ dst.is_tagged, 0 ] Commands: - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] Dst: Object: tagger Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] - reward: -2 - incr: times_tagged - eq: Arguments: [ times_tagged, 3 ] Commands: - set: [ player_done, 1 ] + - decr: tagged_count - reward: -5 - remove: true diff --git a/resources/games/Multi-Agent/robot_tag_8.yaml b/resources/games/Multi-Agent/robot_tag_8.yaml new file mode 100644 index 000000000..887c8940e --- /dev/null +++ b/resources/games/Multi-Agent/robot_tag_8.yaml @@ -0,0 +1,266 @@ +Version: "0.1" +Environment: + Name: Robot Tag 12v12 + Description: Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + Observers: + Block2D: + TileSize: 24 + Sprite2D: + TileSize: 24 + BackgroundTile: oryx/oryx_fantasy/floor1-1.png + Vector: + IncludePlayerId: true + IncludeVariables: true + Variables: + - Name: player_done + InitialValue: 0 + PerPlayer: true + - Name: tagged_count + InitialValue: 0 + Player: + Count: 8 + Observer: + RotateWithAvatar: true + TrackAvatar: true + Height: 9 + Width: 9 + OffsetX: 0 + OffsetY: 0 + AvatarObject: tagger + Termination: + End: + - eq: [ tagged_count, 0 ] + + Levels: + - | + W W W W W W W W W + W . . f2 . f3 . . W + W . . . . . . . W + W f1 . . . . . f4 W + W . . . . . . . W + W . . . . . . . W + W f8 . . . . . f5 W + W . . . . . . . W + W . . f7 . f6 . . W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . . . . . . . . . . . . . . . . . W + W . . f2 . . . . . . . . . . . . . . f3 . . W + W . f1 . . . . . . . . . . . . . . . . f4 . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . W W W W W W . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . f8 . . . . . . . . . . . . . . . . f5 . W + W . . f7 . . . . . . . . . . . . . . f6 . . W + W . . . . . . . . . . . . . . . . . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . . . . . . m . . . . W + W . . f2 . m . . . . . . . . . . m . f3 . . W + W . f1 . . m . . . . . . . . . . m . . f4 . W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . W . . . . . . W . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W . . . . . . . . . . . . . . . . . . . . W + W m m m m m . . W W W W W W . . m m m m m W + W . . . . m . . . . . . . . . . m . . . . W + W . . . . m . . . . . . . . . . m . . . . W + W . f8 . . m . . . . . . . . . . m . . f5 . W + W . . f7 . m . . . . . . . . . . m . f6 . . W + W . . . . m . . . . . . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f2 . . . . m . . m . . . . . . . . . . m . . m . . . . f3 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . f1 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f4 . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . W . . . . . . W . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . . . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . . . W + W . f8 . . . . . . . . m . . m . . . . . . . . . . m . . m . . . . . . . . f5 . W + W m m m m m . . . . . m . . m . . W W W W W W . . m . . m . . . . . m m m m m W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W . . . . m f7 . . . . m . . m . . . . . . . . . . m . . m . . . . f6 m . . . . W + W . . . . m . . . . . m . . m . . . . . . . . . . m . . m . . . . . m . . . . W + W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W + + +Actions: + + # Taggers have a random chance of starting in a tagged state + - Name: initialize_is_tagged + InputMapping: + Internal: true + Inputs: + 1: + Description: Initialize Tagged + 2: + Description: Initialize Not Tagged + VectorToDest: [ -1, 0 ] + + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src._playerId, dst._playerId ] + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - incr: tagged_count + Dst: + Object: tagger + + - Name: tag + Behaviours: + - Src: + Object: tagger + Preconditions: + - eq: [ src.is_tagged, 1 ] + - eq: [ dst.is_tagged, 0 ] + Commands: + - reward: 2 + - set_tile: 0 + - set: [ is_tagged, 0 ] + Dst: + Object: tagger + Commands: + - set_tile: 1 + - set: [ is_tagged, 1 ] + - reward: -2 + - incr: times_tagged + - eq: + Arguments: [ times_tagged, 3 ] + Commands: + - set: [ player_done, 1 ] + - decr: tagged_count + - reward: -5 + - remove: true + + - Name: move + Behaviours: + - Src: + Object: [tagger, moveable_wall] + Commands: + - mov: _dest # mov will move the object, _dest is the destination location of the action + Dst: + Object: _empty + + - Src: + Object: tagger + Commands: + - mov: _dest + Dst: + Object: moveable_wall + Commands: + - cascade: _dest + +Objects: + - Name: tagger + MapCharacter: f + InitialActions: + - Action: initialize_is_tagged + Randomize: true + Variables: + - Name: is_tagged + InitialValue: 0 + - Name: times_tagged + InitialValue: 0 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/robot1.png + - Image: oryx/oryx_fantasy/avatars/fireguy1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 0.5 + - Shape: triangle + Color: [ 0.9, 0.2, 0.2 ] + Scale: 1.0 + + - Name: moveable_wall + MapCharacter: m + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/wall4-0.png + Block2D: + - Color: [ 0.8, 0.8, 0.8 ] + Shape: square + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall2-0.png + - oryx/oryx_fantasy/wall2-1.png + - oryx/oryx_fantasy/wall2-2.png + - oryx/oryx_fantasy/wall2-3.png + - oryx/oryx_fantasy/wall2-4.png + - oryx/oryx_fantasy/wall2-5.png + - oryx/oryx_fantasy/wall2-6.png + - oryx/oryx_fantasy/wall2-7.png + - oryx/oryx_fantasy/wall2-8.png + - oryx/oryx_fantasy/wall2-9.png + - oryx/oryx_fantasy/wall2-10.png + - oryx/oryx_fantasy/wall2-11.png + - oryx/oryx_fantasy/wall2-12.png + - oryx/oryx_fantasy/wall2-13.png + - oryx/oryx_fantasy/wall2-14.png + - oryx/oryx_fantasy/wall2-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square diff --git a/src/Griddly/Core/GDY/GDYFactory.cpp b/src/Griddly/Core/GDY/GDYFactory.cpp index c8955806b..f343b3ab5 100644 --- a/src/Griddly/Core/GDY/GDYFactory.cpp +++ b/src/Griddly/Core/GDY/GDYFactory.cpp @@ -836,7 +836,7 @@ std::string GDYFactory::getAvatarObject() const { return avatarObject_; } -uint32_t GDYFactory::getNumLevels() const { +uint32_t GDYFactory::getLevelCount() const { return mapLevelGenerators_.size(); } diff --git a/src/Griddly/Core/GDY/GDYFactory.hpp b/src/Griddly/Core/GDY/GDYFactory.hpp index bf0569e01..7ce748447 100644 --- a/src/Griddly/Core/GDY/GDYFactory.hpp +++ b/src/Griddly/Core/GDY/GDYFactory.hpp @@ -60,7 +60,7 @@ class GDYFactory { virtual void setMaxSteps(uint32_t maxSteps); virtual std::string getName() const; - virtual uint32_t getNumLevels() const; + virtual uint32_t getLevelCount() const; virtual uint32_t getPlayerCount() const; virtual std::vector getExternalActionNames() const; diff --git a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp index 8146dcce4..b90aa6134 100644 --- a/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp +++ b/tests/src/Griddly/Core/GDY/GDYFactoryTest.cpp @@ -61,7 +61,7 @@ TEST(GDYFactoryTest, loadEnvironment) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test Environment"); - ASSERT_EQ(gdyFactory->getNumLevels(), 1); + ASSERT_EQ(gdyFactory->getLevelCount(), 1); auto globalVariableDefinitions = gdyFactory->getGlobalVariableDefinitions(); ASSERT_EQ(globalVariableDefinitions["global_variable1"].initialValue, 50); @@ -92,7 +92,7 @@ TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_playerId) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getVectorObserverConfig(); @@ -122,7 +122,7 @@ TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_variables) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getVectorObserverConfig(); @@ -152,7 +152,7 @@ TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_rotation) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getVectorObserverConfig(); @@ -184,7 +184,7 @@ TEST(GDYFactoryTest, loadEnvironment_VectorObserverConfig_playerId_rotation_vari gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getVectorObserverConfig(); @@ -208,7 +208,7 @@ TEST(GDYFactoryTest, loadEnvironment_Observer) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test Environment"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto observationDefinition = gdyFactory->getPlayerObserverDefinition(); @@ -240,7 +240,7 @@ TEST(GDYFactoryTest, loadEnvironment_BlockObserverConfig) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getBlockObserverConfig(); @@ -273,7 +273,7 @@ TEST(GDYFactoryTest, loadEnvironment_SpriteObserverConfig) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getSpriteObserverConfig(); @@ -312,7 +312,7 @@ TEST(GDYFactoryTest, loadEnvironment_IsometricSpriteObserverConfig) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto config = gdyFactory->getIsometricSpriteObserverConfig(); @@ -339,7 +339,7 @@ TEST(GDYFactoryTest, loadEnvironment_ObserverNoAvatar) { gdyFactory->loadEnvironment(environmentNode); ASSERT_EQ(gdyFactory->getName(), "Test Environment"); - ASSERT_EQ(gdyFactory->getNumLevels(), 0); + ASSERT_EQ(gdyFactory->getLevelCount(), 0); auto observationDefinition = gdyFactory->getPlayerObserverDefinition(); From 7694a392dc0bb25322fee0eb00f25212b9c14c1e Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 8 Mar 2021 16:20:05 +0000 Subject: [PATCH 28/34] basic documentation for rllib working --- .../Foragers/img/Foragers-level-Block2D-0.png | Bin 0 -> 1253 bytes .../Foragers/img/Foragers-level-Block2D-1.png | Bin 0 -> 1270 bytes .../Foragers/img/Foragers-level-Block2D-2.png | Bin 0 -> 2170 bytes .../img/Foragers-level-Sprite2D-0.png | Bin 0 -> 5203 bytes .../img/Foragers-level-Sprite2D-1.png | Bin 0 -> 7409 bytes .../img/Foragers-level-Sprite2D-2.png | Bin 0 -> 12498 bytes .../Foragers/img/Foragers-level-Vector-0.png | Bin 0 -> 283 bytes .../Foragers/img/Foragers-level-Vector-1.png | Bin 0 -> 305 bytes .../Foragers/img/Foragers-level-Vector-2.png | Bin 0 -> 603 bytes .../img/Foragers-tile-fixed_wall-Block2D.png | Bin 0 -> 91 bytes .../img/Foragers-tile-fixed_wall-Sprite2D.png | Bin 0 -> 361 bytes .../img/Foragers-tile-fixed_wall-Vector.png | Bin 0 -> 77 bytes .../img/Foragers-tile-harvester-Block2D.png | Bin 0 -> 204 bytes .../img/Foragers-tile-harvester-Sprite2D.png | Bin 0 -> 530 bytes .../img/Foragers-tile-harvester-Vector.png | Bin 0 -> 78 bytes .../img/Foragers-tile-potion1-Block2D.png | Bin 0 -> 91 bytes .../img/Foragers-tile-potion1-Sprite2D.png | Bin 0 -> 347 bytes .../img/Foragers-tile-potion1-Vector.png | Bin 0 -> 78 bytes .../img/Foragers-tile-potion2-Block2D.png | Bin 0 -> 88 bytes .../img/Foragers-tile-potion2-Sprite2D.png | Bin 0 -> 343 bytes .../img/Foragers-tile-potion2-Vector.png | Bin 0 -> 78 bytes .../img/Foragers-tile-potion3-Block2D.png | Bin 0 -> 97 bytes .../img/Foragers-tile-potion3-Sprite2D.png | Bin 0 -> 343 bytes .../img/Foragers-tile-potion3-Vector.png | Bin 0 -> 77 bytes docs/games/Foragers/index.rst | 375 ++++++++++++++++++ docs/games/img/Foragers-taster.png | Bin 0 -> 12498 bytes docs/games/index.rst | 12 +- docs/rllib/intro/index.rst | 33 +- .../img/Foragers-level-Sprite2D-1.png | Bin 0 -> 7409 bytes docs/rllib/multi-agent/index.rst | 99 ++++- python/examples/rllib/foragers.yaml | 197 --------- python/examples/rllib/repro/reproduce.py | 35 -- ...iagent_taggers.py => rllib_multi_agent.py} | 9 +- 33 files changed, 521 insertions(+), 239 deletions(-) create mode 100644 docs/games/Foragers/img/Foragers-level-Block2D-0.png create mode 100644 docs/games/Foragers/img/Foragers-level-Block2D-1.png create mode 100644 docs/games/Foragers/img/Foragers-level-Block2D-2.png create mode 100644 docs/games/Foragers/img/Foragers-level-Sprite2D-0.png create mode 100644 docs/games/Foragers/img/Foragers-level-Sprite2D-1.png create mode 100644 docs/games/Foragers/img/Foragers-level-Sprite2D-2.png create mode 100644 docs/games/Foragers/img/Foragers-level-Vector-0.png create mode 100644 docs/games/Foragers/img/Foragers-level-Vector-1.png create mode 100644 docs/games/Foragers/img/Foragers-level-Vector-2.png create mode 100644 docs/games/Foragers/img/Foragers-tile-fixed_wall-Block2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-fixed_wall-Sprite2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-fixed_wall-Vector.png create mode 100644 docs/games/Foragers/img/Foragers-tile-harvester-Block2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-harvester-Sprite2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-harvester-Vector.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion1-Block2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion1-Sprite2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion1-Vector.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion2-Block2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion2-Sprite2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion2-Vector.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion3-Block2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion3-Sprite2D.png create mode 100644 docs/games/Foragers/img/Foragers-tile-potion3-Vector.png create mode 100644 docs/games/Foragers/index.rst create mode 100644 docs/games/img/Foragers-taster.png create mode 100644 docs/rllib/multi-agent/img/Foragers-level-Sprite2D-1.png delete mode 100644 python/examples/rllib/foragers.yaml delete mode 100644 python/examples/rllib/repro/reproduce.py rename python/examples/rllib/{rllib_multiagent_taggers.py => rllib_multi_agent.py} (90%) diff --git a/docs/games/Foragers/img/Foragers-level-Block2D-0.png b/docs/games/Foragers/img/Foragers-level-Block2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..11fee696c24c86abcf107cfaa3cbb04f6d59025d GIT binary patch literal 1253 zcmeAS@N?(olHy`uVBq!ia0vp^H-Pv92NRHdyVv$10|Uz%PZ!6Kid%2*JPecy6lr_7 zI$1P=ZQ91#fMa~AF|Rl9Xeux3{bMjQagIy)w>hWfKH2Q&a8x@qo4?>$mHGMN=Iq_y z1r%Hal$=-;mGDv;@{itC+>z~<`>5r&`*EiEyPvzdt*+PnDNotE)j50b)^E=b9sIl} zF*skp-R$eNTkpB`|JNN_^s7?h^WLx5=6$!1%KrQ1#Z0}6kIp}rU%PcBf7QNs>?%7x zc$rOla7f#GpL@;x!=JfxZvK{;7r#O_Y5$A$H?GL^NgfsK{*lPqn>5u@#Gg;fBC7TpQc(^{hUh_)X)P-XrzXVBr~oKAanu-<_d-F7cIy zn%<-7v%$g*fj#^i`F{$nGit9~JY#DI_w{ocVKWcE3FP71$p7Do?R?@Tjb%EIwwHm0 zuLSn+Y>fYUT5?a;#>Eo*JH&P71g-(;ln_h0e`c;h&Uq$x!wNzDduEFxQu!vk8LAyQ z5FF^X@sv#7_rs@7g&KE-J~K!bVRquNSIH9jzumGY=EwwFuxO14bKm5BXN$Anmej=j zw}1QKMDafR_9^n${U4uR=o@@yXPW)Kk4$?z!+&mI{x)sRGre_N`z2lWw{%MX-oWJK z(xKqe!hwsDV`|~pZ+qvJ`8TN&dw<98UvAB7`e!R9*wP{3*ujB9VKHXO9CrSL;qz=* z#00xf>@?l~?%yeck70&<<%VGwA`uc2!0*>TJED!RsP=lAszC+CMP$t z*#AuKb7X(B{966H<=1YN1SR&0eLo+Z{njK){fW8`p9=noBao-?LGhS zXX`D+x=l518_&q_p{LEdos}T*XZ=aPl>U|e?*XM*v#v-*y%_7o5ovsr-3`kH?IZ0# zDdl)il2^z5Tb3YkV|T+s!Ton^K;rB@Nj2a^reoZmiIzM+KFT%jN_}RKE)p#EG1?lG znt>_Xt!+oq)t;o8mOc_kf4J^RcnGrgjP%hB0?yygXiS@V_(dR3+>!3>XF;iaNnj7> z$C5j_S&@?4QjXuvb5?9yGYgcUB^OHwBt4LkIvQ0hVbPV?EwL#bl!)8B=NUva UywTVKEWQ{#UHx3vIVCg!0Lw%WNdN!< literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Block2D-1.png b/docs/games/Foragers/img/Foragers-level-Block2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e608df94f1053fee964b38a231e74e486e64f9ec GIT binary patch literal 1270 zcmeAS@N?(olHy`uVBq!ia0vp^H-Pv92NRHdyVv$10|U!mPZ!6Kid%2*Ui6iA6luFC z?IOC6N8|9y!>Mbw^(^cSaEo#CkghCHQWQzp_`c`4)pHw3_KuHrKOHBs)kjWKeQ+ty zhLuCGr9;56g8+3Rpnd)8hp(;Ptlxe1m&@y4&zkcDe|Lxp*5|*8ect&t%I0nI^?xzf z9?#A{X(e87{VY3Mu>ZTg@*>%HF)xL5|5g2w6x?n0qUtV>N5SK{K9e3Cv-bYiQ!(f8 zbn_WE3;E*jt&mN!e%U`Gi|=^PqYl@atxawlPs#Mj9PLxww)uXe=d*$X8fEcE>fg-| zTw~OJ*llBYhwj(AJATM~lRLVv`^ULYl1H_QEz(4Gx7^sZ*I0bc;dP5|6bjnku~{6E z%E#W5RM(MvqP<|Et=!6XzNyZ?C}ziFVy;g8a1oaL{^_k{H3u>R^3+S-MRnMxAj-VP3xHP z+;qu(%k_UXh4<=xD|R_jwZpnzYW}y4HqUEf|NTB@{z7x#>E*gf*YxuvOpJ~MUt-N4x= zbH4sMasQIs#lxX?_F{rsI**o|`&r7*KhLh4P^$TpJ}2_#v7N%*9j<#CR9}DXSpVfu z{w|`~;g>;VWOhYq*!^x#X!KDR>+A!Bx`?f`SlHkUUBbDqwdRDojt3Xu`=XGe? zUH@bIwsgMyvnm@gY$+VB6mH+-wjJHux}QnvFW#E*c-{54Gg#&){l7MQ{hHTRQ>Sme zbp2}7oA3GiBF&E^*X(~+F1XQJ;;5AFJ^Ob`j|{Rz^4U~&eDq73dAPSc?2qI}i+25d zgUyd+_U#F@K2kpIyvDSdhZPrFBnif^T>wfQ%I+JJJLL1`1?m{L|8?K^Tj^i%e^6?Y zG3(mvbZ1AI+r~38W#~!!$I7|JU8&DDJl80TIl^y#9+X%a7tfG?#L1m5a{5f-_P{q= zJEX6J6Y;&pGh`l}yqyLTpC9;UQ^)*UmY`I;yDtfnM1dB|x}l|RP$C7Y|E8g)57rAz zu*(DIyc3xZa^~f>if609`6gK;-R63Z$p7t@XLt-#&x73UBY#xC`?}z>Q*~~HQl)?r ghoEAUz|3>`_q8o~PVeLP2bO0Hp00i_>zopr08rW|e*gdg literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Block2D-2.png b/docs/games/Foragers/img/Foragers-level-Block2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c9096c51ae8218f086723d7770bed13fbed8b3bf GIT binary patch literal 2170 zcmeAS@N?(olHy`uVBq!ia0y~yVB7%24>*{BBHoMEcQG(3+TY-+p_2zEm2^ zAn@_9H-kb4LlXz-gu$)K+w-^AZm!Rp{MDuQ|NZh^E;7nqE`O>Yzxez|XIk0%@4dw* zk}aqFsO)k6`8#v(pH1H^?`-*b!|TZHci}EmKCeD+Vz8le|NT1GBMVJlxqZ!_dHTV9 zmLDDu58SWWpb!`H?BR2BhP>Tp4xi7rDHAvyThB71J^BBOM6pNHLo-yE4nI#kJu$+t zBlS}}!daAYgF#Fwd`?Z~amy_Nj4Xl< zP=W~8WgMETb$CZV)AI{X5YI$t+dIn~tKDJIE{5bRV$yN$j^I>@?|}~>&XM=?JpZ`c z`rN`+!Z`qY032}fuh#!umw5h(*`3lNK3%*)4f8U>^VDI5ypUsYeg zH`kdPp3q1P>x8XlaZT@ca7uzw-3Ec3kDhp=B@}FqKofHieitHntX2e`P!B}?lze3H z?(k94Gr?g*CfFeGv%srLF8)p-ffNVMTsV@QTGg%Uncumq)wVxeH<>^d$DOtM#f~^n zaTZx`Vc?`0c&l`=tHAz}0I^0#%{?DaTS>)+)OUQUF7N0#9Ld|b(D%+>S*1sJvQ)z2 zJHo#&n8?vHqkXMg(&G7waj3<8zK)}^poAg+?4BcH+#gSCnFuvL{Ji0*#~O(v`Q{*f z_Zm#ylfJfAl=4e;9{5msBS+;L*P{bp(^Z%jUyt89F```YUPP0dl0l8!Hp$M=CX1}^ zlAQ-$+zn4x35)5tpXUHHeK+Ikz9WAW?rr?h<6^)cZIiOD)uP}pP}z;I;rS|R>>uB) zO;>5EwD|3@W|M&XTRV`J-&(y#&M5fBRD#Oz&p_?16~`Ze3hJB>6UrQYZkB_@jr)!~ zX|4Ev4P1cNsyt))cqdl=);ynOCt7}Yw=&aequQIL?=$auM0aT3w^&~Po!6$ESNZ+^ n6)O5J4ej5^s>L_h)H9^Y23_M_E*l1HQ!seC`njxgN@xNA01w;7 literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Sprite2D-0.png b/docs/games/Foragers/img/Foragers-level-Sprite2D-0.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ac8f4e8597c77314d3dd4c099a899b69d0fced GIT binary patch literal 5203 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 Y!C{|kr%@Kb&q4-sV{4-t1Gh*210!&3WdHyG literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Sprite2D-1.png b/docs/games/Foragers/img/Foragers-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e798a62eed7a17dad8f2116067855e0a505cd7e5 GIT binary patch literal 7409 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 KM->{@@Ba&Exb2<* literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Sprite2D-2.png b/docs/games/Foragers/img/Foragers-level-Sprite2D-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c201ad7747186817af6444272265ab197c87f896 GIT binary patch literal 12498 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 WhdTCW;QuNd(o(;tR&eXVv;P967dA%# literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Vector-0.png b/docs/games/Foragers/img/Foragers-level-Vector-0.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad3c148ba4996d6fcb314da22a0d5f406c6ce60 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^Q9zu+!2~2bcWvkZQja`c978H@y}e_|b=ZN2#Ub~@ z|NU!9G@q)uv>V^v^^sSILo$Asw_T;;fktL_J{gOG2RBZb+WReAxK*27PSbQ>v3qHT z7)Tf>f4orn&e?S){nmx6`OY>$m0RdtzjnZSolbVl#W3|%&YM9BK?eCOjh`9${N$$4 z_OydAqYo%T3|g1n32vT$==S29m;ls$HA-HK?SZy7vY{an^LB{Ts5y?k#b literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Vector-1.png b/docs/games/Foragers/img/Foragers-level-Vector-1.png new file mode 100644 index 0000000000000000000000000000000000000000..af0447bf85d2883116a9c3ab16eb24f199972576 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^Q9zu+!2~2bcWvkZQa?Oh978H@y}jee)ndTIdLiw@ zzwmQ!V%!X;iB;a@oBDC$0reFVEw4n{vse^7IMB$<&L^Y&YDInD3a9M88y}`6P5E!> zTrMFd1Cc#mCipHXJnHzlbMs11Yrp1$3bXgAmiqAQ`Fd`Xs1B0q_K+%bn=tpy#SsV- zfMWVqerwJx3N43PXHih2JGHDS^`~6ul6M9=FlVg%GWE{Qh^S>KLTkFsrzPEDo_Epm s*PraLT_D$j)hBzlu782#oTp#e!*9u-W6}R{0T>1hp00i_>zopr00v2Yh5!Hn literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-level-Vector-2.png b/docs/games/Foragers/img/Foragers-level-Vector-2.png new file mode 100644 index 0000000000000000000000000000000000000000..a498461217c127e7954efdef2737ed6746dd85be GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0vp^TY&fk2NRGy=O1&Qfq_ZM)5S5Q;?~={j(vv=cpNTD zJpC7LChl-cVSM-75~C?)}tceE7+M{h~lwn9_nxNBLe(t6uQurL>79Oc1O;Bmds_Yk~h7_S@{S zIJJ1Sa`OJ#+p^(@pCD`iDqpbo=M{GMy_y#1cG)TEu zhrkiX>TcsDfq4s459=PzTUXPph8&{;>~Ghen~+r0fD~p&9OL5+m$*)vGNI`bFwro0 My85}Sb4q9e07mHUN&o-= literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-fixed_wall-Block2D.png b/docs/games/Foragers/img/Foragers-tile-fixed_wall-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..23b17524e93d7eca0cd517524637f785cd4be25b GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9N}eu`Ar-fh*Q{TkzJM(uX@QW0 nX~vZWfz58bQb2~nCq{-FI*e;Cu6rX7RL$V&>gTe~DWM4f%-9)~ literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-fixed_wall-Sprite2D.png b/docs/games/Foragers/img/Foragers-tile-fixed_wall-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..b7449ca36cff01d8cb86c8259be90c1989e5ddc4 GIT binary patch literal 361 zcmV-v0ha!WP)bVd1$_pJ_O7aR~uX$7+hR2W}--OV(xsZWUY2)q}8sA zoU^Q9iC$li^!JBt*;L<`&#oQl+lYvC?I0q%+k~JNWq;UyoHFvbr)vkIAV);Xrh-Fy zjVj8fVubuafiVqBumU}zA~G*AREGl9qB!QaW)-QdWPb&sD78oU3uBwqP0+uT#yNN) z!~@~r9g3YPhk_BatID;n^OaO~nn@l96e>NXCSOuoc)$*t6+Jv7oNPDZ?$bgO#XoGY z&2baEI42p&%+a@;3v1V>Rm4|(&bTqfkkf*=1C7MRTEC+onl1Nk)O8hy00000NkvXX Hu0mjfd*h!? literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-fixed_wall-Vector.png b/docs/games/Foragers/img/Foragers-tile-fixed_wall-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..c23edfdd306daf6343a992c6f6f5f7ee99bfe1f0 GIT binary patch literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1|M@?kkv}0RL1OAL Z7KRdbCZVD$UN?dA44$rjF6*2UngCME5~u(G literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-harvester-Block2D.png b/docs/games/Foragers/img/Foragers-tile-harvester-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..3a720c7963eac59fce8975c0d23b284bf933fa7a GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9lRaG=Ln>~qy>OAYMM0qD;m7l( zg{cv5Z%s~(k^i1x!jhUO{=AXn%p`rUw>Nf1ZOnc8_}-}{xqm%3{e5lz(Nej6ZhN21 zoKMQ%CVZ`n@d?qa5mD?5PMC1nRq*MSte)aO+Cp=V8v%tBj_>Rj017R!ysE%?E_tS; yU#AjK$l&D80FY3X=9vPoE7^-u=RS`4$t=0eBvjn~;TE9t7(8A5T-G@yGywpn3ssZ= literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-harvester-Sprite2D.png b/docs/games/Foragers/img/Foragers-tile-harvester-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..90d76eace71b7d288fad132bc412c2f3c8ac0165 GIT binary patch literal 530 zcmV+t0`2{YP)qULCXxlan zL!}gX$~{h|%~7NEdVLvbg)xg4b67!FIjyy26!66?vNT4A6_9?$m@LcaUyeu=8A+_? zhp+ROr!#A{h&nTxH3#QG+WQW+_>0xQbSktieG8OPFl!C~c04%NcZ;HGR7GcZv*y5w zlu___cjG|s2OXU)Ez2^B=Pz#`-$Wk|FpZHZRjs+F&JCHySk;LVV+$RNXkgk1FY<5S7s@9aXw7$1w#+Z4Qy(9(v*#gcjM`Vpj`}ytXWlwm! z!6SAWBTMG6_DSK)0}n_l#bbb3jN2y#pOw*QBu`0Sj=ssX$q^|X$pV1+NnO*SkzZ`8 zF5&1-xGUxdkq}kwYA2UD?hYVF{B_oN!YZYP!yyk7lB@vz?~LRmW8q0V*=SDG57PKB UvGR%W^Z)<=07*qoM6N<$g5OH>v;Y7A literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-harvester-Vector.png b/docs/games/Foragers/img/Foragers-tile-harvester-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..5423baded82d810b8792b647a8af95a95818daab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1rzHG1-ym&Z_<&V8 aREoh~k5M>u-(@$T0tQc4KbLh*2~7Y}J`x!K literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion1-Block2D.png b/docs/games/Foragers/img/Foragers-tile-potion1-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd135eb19450926656036b8b8ede3afd8337b02 GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9N}eu`Ar-fhQxYr=JMbPj;-G4f mbzz0$Vzyo>6AC>c}$ literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion1-Sprite2D.png b/docs/games/Foragers/img/Foragers-tile-potion1-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..834b8a689718542783aa4ea4004f22174523562b GIT binary patch literal 347 zcmV-h0i^zkP)18o-AeMhHL2hhvfwo(COAoyi{Rh_ia88g z_x?S+{7{QQo+yLv_1(+P&W5@xDTz~Y-qgOSaNo)6%99XAQ>RW97Z+z>U|6$e&C8cB z$&59K2}GG4lrl t0|O(jq84jR<4N2s__N4pna#k!003@rXQG{*J+A-&002ovPDHLkV1kejk_!L; literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion1-Vector.png b/docs/games/Foragers/img/Foragers-tile-potion1-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..758055b8f4658f1313667b0074ee7ebc3484227c GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1|2h9*KBIX;(gAL_ bRVEC@985xOn|n6{6)6A*(rPk=%07{ec*#WqGjr3{{~elF{r5}E*SR2Ovs literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion2-Sprite2D.png b/docs/games/Foragers/img/Foragers-tile-potion2-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..0eab7039cb290c3cdb02dee174b9c4ddd31b8422 GIT binary patch literal 343 zcmV-d0jU0oP)18o-AeMhHL2hhvfwo(COAoyi{Rh_ia88g z_x?S+{7{QQo+yLv_1(+P&W5@xDTz~Y-qgOSaNo)6%99XAQ>RW97Z+z>U|6$e&C8cB z$&59KlvpAt{R>Cy)z9 zMo^Z$J%f>v3VDHz>>Y!1=LmX`sN9cm8hN?@P<*_Fg$0Ct;=~CE(cb_7002ovPDHLkV1n9WlUD!$ literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion2-Vector.png b/docs/games/Foragers/img/Foragers-tile-potion2-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..50a43e1f746ddb37e467f9db2d780a8a55e5bc8d GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1r~LRopV2%a=>WIe aDiekxP9`Dydn!#p1q`09elF{r5}E*IP!gj6 literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion3-Block2D.png b/docs/games/Foragers/img/Foragers-tile-potion3-Block2D.png new file mode 100644 index 0000000000000000000000000000000000000000..29b9b6ca470626d44dec53e0d0fba4351a9d8563 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz98lEnWAr-fh6C`pP1ij`jJisH& r1_WwZJ2S2<2yAxal`;h~7BVv&3}DoFtP&^-)WhKE>gTe~DWM4f2bmbA literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion3-Sprite2D.png b/docs/games/Foragers/img/Foragers-tile-potion3-Sprite2D.png new file mode 100644 index 0000000000000000000000000000000000000000..6fd6073a31c3e557491aa7fb52ebfb90e26b7e5c GIT binary patch literal 343 zcmV-d0jU0oP)18o-AeMhHL2hhvfwo(COAoyi{Rh_ia88g z_x?S+{7{QQo+yLv_1(+P&W5@xDTz~Y-qgOSaNo)6%99XAQ>RW97Z+z>U|6$e&C8cB z$&59K2}GG4lrl0|O(j pq84jR<4N2s__N4pna#k!008s&X1|t4D4YNQ002ovPDHLkV1gNdisb+R literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/img/Foragers-tile-potion3-Vector.png b/docs/games/Foragers/img/Foragers-tile-potion3-Vector.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ac2796817cbb2ec5f405e43f3a93bb6196fe93 GIT binary patch literal 77 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih}1r~EkIz;0k@(BqZN Z#NeRIDEyr%>>N;@!PC{xWt~$(6972r5s3f* literal 0 HcmV?d00001 diff --git a/docs/games/Foragers/index.rst b/docs/games/Foragers/index.rst new file mode 100644 index 000000000..332339b75 --- /dev/null +++ b/docs/games/Foragers/index.rst @@ -0,0 +1,375 @@ +.. _doc_foragers: + +Foragers +======== + +.. code-block:: + + Multi-Agent/foragers.yaml + +Description +------------- + +A very simple multi-agent game. Agents must collect the coloured potions + +Levels +--------- + +.. list-table:: Levels + :header-rows: 1 + + * - + - Vector + - Sprite2D + - Block2D + * - .. list-table:: + + * - Level ID + - 0 + * - Size + - 9x10 + - .. thumbnail:: img/Foragers-level-Vector-0.png + - .. thumbnail:: img/Foragers-level-Sprite2D-0.png + - .. thumbnail:: img/Foragers-level-Block2D-0.png + * - .. list-table:: + + * - Level ID + - 1 + * - Size + - 9x10 + - .. thumbnail:: img/Foragers-level-Vector-1.png + - .. thumbnail:: img/Foragers-level-Sprite2D-1.png + - .. thumbnail:: img/Foragers-level-Block2D-1.png + * - .. list-table:: + + * - Level ID + - 2 + * - Size + - 18x20 + - .. thumbnail:: img/Foragers-level-Vector-2.png + - .. thumbnail:: img/Foragers-level-Sprite2D-2.png + - .. thumbnail:: img/Foragers-level-Block2D-2.png + +Code Example +------------ + +The most basic way to create a Griddly Gym Environment. Defaults to level 0 and SPRITE_2D rendering. + +.. code-block:: python + + + import gym + import griddly + + if __name__ == '__main__': + + env = gym.make('GDY-Foragers-v0') + env.reset() + + # Replace with your own control algorithm! + for s in range(1000): + obs, reward, done, info = env.step(env.action_space.sample()) + for p in range(env.player_count): + env.render(observer=p) # Renders the environment from the perspective of a single player + + env.render(observer='global') # Renders the entire environment + + if done: + emv.reset() + + +Objects +------- + +.. list-table:: Tiles + :header-rows: 2 + + * - Name -> + - harvester + - potion1 + - potion2 + - potion3 + - fixed_wall + * - Map Char -> + - `f` + - `b` + - `r` + - `g` + - `W` + * - Vector + - .. image:: img/Foragers-tile-harvester-Vector.png + - .. image:: img/Foragers-tile-potion1-Vector.png + - .. image:: img/Foragers-tile-potion2-Vector.png + - .. image:: img/Foragers-tile-potion3-Vector.png + - .. image:: img/Foragers-tile-fixed_wall-Vector.png + * - Sprite2D + - .. image:: img/Foragers-tile-harvester-Sprite2D.png + - .. image:: img/Foragers-tile-potion1-Sprite2D.png + - .. image:: img/Foragers-tile-potion2-Sprite2D.png + - .. image:: img/Foragers-tile-potion3-Sprite2D.png + - .. image:: img/Foragers-tile-fixed_wall-Sprite2D.png + * - Block2D + - .. image:: img/Foragers-tile-harvester-Block2D.png + - .. image:: img/Foragers-tile-potion1-Block2D.png + - .. image:: img/Foragers-tile-potion2-Block2D.png + - .. image:: img/Foragers-tile-potion3-Block2D.png + - .. image:: img/Foragers-tile-fixed_wall-Block2D.png + + +Actions +------- + +move +^^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +init_potion +^^^^^^^^^^^ + +:Internal: This action can only be called from other actions, not by the player. + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - The only action here is to increment the potion count + + +gather +^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - Action Id + - Mapping + * - 1 + - Left + * - 2 + - Up + * - 3 + - Right + * - 4 + - Down + + +YAML +---- + +.. code-block:: YAML + + Version: "0.1" + Environment: + Name: Foragers + Description: A very simple multi-agent game. Agents must collect the coloured potions + Observers: + Sprite2D: + TileSize: 24 + BackgroundTile: gvgai/oryx/grass_15.png + Block2D: + TileSize: 24 + Player: + Count: 4 + Observer: + TrackAvatar: true + Height: 5 + Width: 5 + OffsetX: 0 + OffsetY: 0 + AvatarObject: harvester + Variables: + - Name: potion_count + InitialValue: 0 + Termination: + End: + - eq: [potion_count, 0] + + Levels: + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . . . . . . . W + W . . r b r . . W + W . . r g r . . W + W . . r g r . . W + W . . r b r . . W + W . . . . . . . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W + W f1 . . . . . f2 W + W . W W . W W . W + W . W r b r W . W + W . . r g r . . W + W . W r g r W . W + W . W r b r W . W + W . W W . W W . W + W f4 . . . . . f3 W + W W W W W W W W W + - | + W W W W W W W W W W W W W W W W W W + W f1 . . . . . . W W . . . . . . f2 W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W W W W W W W W W W W W W W W W W W + W W W W W W W W W W W W W W W W W W + W . . . . . . . W W . . . . . . . W + W . . . . . . . W W . . . . . . . W + W . . r b r . . W W . . r b r . . W + W . . r g r . . W W . . r g r . . W + W . . r g r . . W W . . r g r . . W + W . . r b r . . W W . . r b r . . W + W . . . . . . . W W . . . . . . . W + W f4 . . . . . . W W . . . . . . f3 W + W W W W W W W W W W W W W W W W W W + + + Actions: + - Name: init_potion + InputMapping: + Internal: true + Inputs: + 1: + Description: "The only action here is to increment the potion count" + Behaviours: + - Src: + Object: [ potion1, potion2, potion3 ] + Commands: + - incr: potion_count + Dst: + Object: [ potion1, potion2, potion3 ] + + - Name: gather + Behaviours: + - Src: + Object: harvester + Commands: + - reward: 1 + Dst: + Object: [potion1, potion2, potion3] + Commands: + - decr: value + - eq: + Arguments: [ value, 0 ] + Commands: + - decr: potion_count + - remove: true + + - Name: move + Behaviours: + - Src: + Object: harvester + Commands: + - mov: _dest + Dst: + Object: _empty + + Objects: + - Name: harvester + MapCharacter: f + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/avatars/man1.png + Block2D: + - Shape: triangle + Color: [ 0.2, 0.2, 0.9 ] + Scale: 1.0 + + - Name: potion1 + MapCharacter: b + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 5 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-0.png + Scale: 0.5 + Block2D: + - Color: [ 0.0, 0.0, 0.8 ] + Shape: square + + - Name: potion2 + MapCharacter: r + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 10 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-2.png + Scale: 0.8 + Block2D: + - Color: [ 0.8, 0.0, 0.0 ] + Shape: square + + - Name: potion3 + MapCharacter: g + InitialActions: + - Action: init_potion + ActionId: 1 + Variables: + - Name: value + InitialValue: 20 + Observers: + Sprite2D: + - Image: oryx/oryx_fantasy/potion-3.png + Scale: 1.0 + Block2D: + - Color: [ 0.0, 0.8, 0.0 ] + Shape: square + Scale: 0.8 + + - Name: fixed_wall + MapCharacter: W + Observers: + Sprite2D: + - TilingMode: WALL_16 + Image: + - oryx/oryx_fantasy/wall9-0.png + - oryx/oryx_fantasy/wall9-1.png + - oryx/oryx_fantasy/wall9-2.png + - oryx/oryx_fantasy/wall9-3.png + - oryx/oryx_fantasy/wall9-4.png + - oryx/oryx_fantasy/wall9-5.png + - oryx/oryx_fantasy/wall9-6.png + - oryx/oryx_fantasy/wall9-7.png + - oryx/oryx_fantasy/wall9-8.png + - oryx/oryx_fantasy/wall9-9.png + - oryx/oryx_fantasy/wall9-10.png + - oryx/oryx_fantasy/wall9-11.png + - oryx/oryx_fantasy/wall9-12.png + - oryx/oryx_fantasy/wall9-13.png + - oryx/oryx_fantasy/wall9-14.png + - oryx/oryx_fantasy/wall9-15.png + Block2D: + - Color: [ 0.5, 0.5, 0.5 ] + Shape: square + + diff --git a/docs/games/img/Foragers-taster.png b/docs/games/img/Foragers-taster.png new file mode 100644 index 0000000000000000000000000000000000000000..c201ad7747186817af6444272265ab197c87f896 GIT binary patch literal 12498 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 WhdTCW;QuNd(o(;tR&eXVv;P967dA%# literal 0 HcmV?d00001 diff --git a/docs/games/index.rst b/docs/games/index.rst index 86730abcc..ef8f4875d 100644 --- a/docs/games/index.rst +++ b/docs/games/index.rst @@ -275,6 +275,7 @@ Multi-Agent Robot_Tag_8v8/index Robot_Tag_12v12/index + Foragers/index Robot_Tag_4v4/index .. list-table:: @@ -294,12 +295,21 @@ Multi-Agent :width: 200 Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. - - **Robot Tag 4v4** + - **Foragers** + + .. image:: img/Foragers-taster.png + :target: Foragers/index.html + :width: 200 + + A very simple multi-agent game. Agents must collect the coloured potions + * - **Robot Tag 4v4** .. image:: img/Robot_Tag_4v4-taster.png :target: Robot_Tag_4v4/index.html :width: 200 Robots start randomly as "tagged" or not, robots can "tag" other robots. Any robot that is "tagged" 3 times dies. + - + - diff --git a/docs/rllib/intro/index.rst b/docs/rllib/intro/index.rst index 3ec106dff..312806b69 100644 --- a/docs/rllib/intro/index.rst +++ b/docs/rllib/intro/index.rst @@ -59,11 +59,40 @@ With these games, the level can be randomized at the end of every episode using If this is set to true then the agent will be placed in one of the random levels described in the GDY file each time the episode restarts. + + +****** +Agents +****** + +We provide a few custom agent models that can be used with any Griddly environment. + +.. _simple_conv_agent: + +Simple Convolutional agent +========================== + +.. code-block:: python + + ModelCatalog.register_custom_model('GAP', GAPAgent) + + ... + + config = { + + 'model': { + 'custom_model': 'GAP' + 'custom_model_config': ..... + } + + ... + + } + .. _gap_agent: -********************** Global Average Pooling -********************** +====================== Griddly environments' observation spaces differ between games, levels and visualization options. In order to handle this in a generic way using neural networks, we provide a Global Average Pooling agent `GAPAgent`, which can be used with any 2D environment with no additional configuration. diff --git a/docs/rllib/multi-agent/img/Foragers-level-Sprite2D-1.png b/docs/rllib/multi-agent/img/Foragers-level-Sprite2D-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e798a62eed7a17dad8f2116067855e0a505cd7e5 GIT binary patch literal 7409 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 KM->{@@Ba&Exb2<* literal 0 HcmV?d00001 diff --git a/docs/rllib/multi-agent/index.rst b/docs/rllib/multi-agent/index.rst index 714593331..c9e93ee9c 100644 --- a/docs/rllib/multi-agent/index.rst +++ b/docs/rllib/multi-agent/index.rst @@ -17,6 +17,103 @@ To register the multi-agent Griddly environment for usage with RLLib, the enviro register_env(env_name, _create_env) + + +*********************** +Handling agent ``done`` +*********************** + +If a multi-agent environment has the conditions in which agents in the environment can be removed, for example they are defeated and are not longer in the episode, a RLLib needs to know that this agent no longer can receive actions. + +Griddly's ``RLlibMultiAgentWrapper`` handles this by detecting a ``player_done_variable``, defined per-player in the GDY. When this variable is set to ``1`` for a player, RLLib will consider this player has been removed. + ************ Full Example -************ \ No newline at end of file +************ + +In this example the :ref:`Foragers ` environment is trained for 10M steps using IMPALA and a `Simple Convolutional Agent `. + +.. figure:: img/Foragers-level-Sprite2D-1.png + :align: center + +The Foragers environment as seen from the "Global Observer" view. + +The following code will run the Foragers example for 10M steps using IMPALA to train. + +.. code-block:: python + + import os + import sys + + import ray + from ray import tune + from ray.rllib.agents.ppo import PPOTrainer + from ray.rllib.models import ModelCatalog + from ray.tune.registry import register_env + + from griddly import gd + from griddly.util.rllib.torch.agents.conv_agent import SimpleConvAgent + from griddly.util.rllib.wrappers.core import RLlibMultiAgentWrapper, RLlibEnv + + if __name__ == '__main__': + sep = os.pathsep + os.environ['PYTHONPATH'] = sep.join(sys.path) + + ray.init(num_gpus=1) + + env_name = 'ray-ma-env' + + # Create the environment and wrap it in a multi-agent wrapper for self-play + def _create_env(env_config): + env = RLlibEnv(env_config) + return RLlibMultiAgentWrapper(env, env_config) + + register_env(env_name, _create_env) + + ModelCatalog.register_custom_model('SimpleConv', SimpleConvAgent) + + max_training_steps = 10000000 + + config = { + 'framework': 'torch', + 'num_workers': 8, + 'num_envs_per_worker': 2, + + 'num_gpus': 1, + + 'model': { + 'custom_model': 'SimpleConv', + 'custom_model_config': {} + }, + 'env': env_name, + 'env_config': { + # in the griddly environment we set a variable to let the training environment + # know if that player is no longer active + # The Foragers game does not have a condition in which agents can be removed. + # 'player_done_variable': 'player_done', + + 'record_video_config': { + 'frequency': 20000 # number of rollouts + }, + + 'random_level_on_reset': True, + 'yaml_file': 'Multi-Agent/foragers.yaml', + 'global_observer_type': gd.ObserverType.SPRITE_2D, + 'max_steps': 500, + }, + 'entropy_coeff_schedule': [ + [0, 0.01], + [max_training_steps, 0.0] + ], + 'lr_schedule': [ + [0, 0.0005], + [max_training_steps, 0.0] + ] + } + + stop = { + 'timesteps_total': max_training_steps, + } + + result = tune.run(PPOTrainer, config=config, stop=stop) + diff --git a/python/examples/rllib/foragers.yaml b/python/examples/rllib/foragers.yaml deleted file mode 100644 index ed442b423..000000000 --- a/python/examples/rllib/foragers.yaml +++ /dev/null @@ -1,197 +0,0 @@ -Version: "0.1" -Environment: - Name: Foragers - Description: - Observers: - Sprite2D: - TileSize: 24 - BackgroundTile: gvgai/oryx/grass_15.png - Block2D: - TileSize: 24 - Player: - Count: 4 - Observer: - TrackAvatar: true - Height: 5 - Width: 5 - OffsetX: 0 - OffsetY: 0 - AvatarObject: harvester - Variables: - - Name: potion_count - InitialValue: 0 - Termination: - End: - - eq: [potion_count, 0] - - Levels: - - | - W W W W W W W W W - W f1 . . . . . f2 W - W . . . . . . . W - W . . r b r . . W - W . . r g r . . W - W . . r g r . . W - W . . r b r . . W - W . . . . . . . W - W f4 . . . . . f3 W - W W W W W W W W W - - | - W W W W W W W W W - W f1 . . . . . f2 W - W . W W . W W . W - W . W r b r W . W - W . . r g r . . W - W . W r g r W . W - W . W r b r W . W - W . W W . W W . W - W f4 . . . . . f3 W - W W W W W W W W W - - | - W W W W W W W W W W W W W W W W W W - W f1 . . . . . . W W . . . . . . f2 W - W . . . . . . . W W . . . . . . . W - W . . r b r . . W W . . r b r . . W - W . . r g r . . W W . . r g r . . W - W . . r g r . . W W . . r g r . . W - W . . r b r . . W W . . r b r . . W - W . . . . . . . W W . . . . . . . W - W . . . . . . . W W . . . . . . . W - W W W W W W W W W W W W W W W W W W - W W W W W W W W W W W W W W W W W W - W . . . . . . . W W . . . . . . . W - W . . . . . . . W W . . . . . . . W - W . . r b r . . W W . . r b r . . W - W . . r g r . . W W . . r g r . . W - W . . r g r . . W W . . r g r . . W - W . . r b r . . W W . . r b r . . W - W . . . . . . . W W . . . . . . . W - W f4 . . . . . . W W . . . . . . f3 W - W W W W W W W W W W W W W W W W W W - - -Actions: - - Name: init_potion - InputMapping: - Internal: true - Inputs: - 1: - Description: "The only action here is to increment the potion count" - Behaviours: - - Src: - Object: [ potion1, potion2, potion3 ] - Commands: - - incr: potion_count - Dst: - Object: [ potion1, potion2, potion3 ] - - - Name: gather - Behaviours: - - Src: - Object: harvester - Commands: - - reward: 1 - Dst: - Object: [potion1, potion2, potion3] - Commands: - - decr: value - - eq: - Arguments: [ value, 0 ] - Commands: - - decr: potion_count - - remove: true - - - Name: move - Behaviours: - - Src: - Object: harvester - Commands: - - mov: _dest - Dst: - Object: _empty - -Objects: - - Name: harvester - MapCharacter: f - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/avatars/man1.png - Block2D: - - Shape: triangle - Color: [ 0.2, 0.2, 0.9 ] - Scale: 1.0 - - - Name: potion1 - MapCharacter: b - InitialActions: - - Action: init_potion - ActionId: 1 - Variables: - - Name: value - InitialValue: 5 - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/potion-0.png - Scale: 0.5 - Block2D: - - Color: [ 0.0, 0.0, 0.8 ] - Shape: square - - - Name: potion2 - MapCharacter: r - InitialActions: - - Action: init_potion - ActionId: 1 - Variables: - - Name: value - InitialValue: 10 - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/potion-2.png - Scale: 0.8 - Block2D: - - Color: [ 0.8, 0.0, 0.0 ] - Shape: square - - - Name: potion3 - MapCharacter: g - InitialActions: - - Action: init_potion - ActionId: 1 - Variables: - - Name: value - InitialValue: 20 - Observers: - Sprite2D: - - Image: oryx/oryx_fantasy/potion-3.png - Scale: 1.0 - Block2D: - - Color: [ 0.0, 0.8, 0.0 ] - Shape: square - Scale: 0.8 - - - Name: fixed_wall - MapCharacter: W - Observers: - Sprite2D: - - TilingMode: WALL_16 - Image: - - oryx/oryx_fantasy/wall9-0.png - - oryx/oryx_fantasy/wall9-1.png - - oryx/oryx_fantasy/wall9-2.png - - oryx/oryx_fantasy/wall9-3.png - - oryx/oryx_fantasy/wall9-4.png - - oryx/oryx_fantasy/wall9-5.png - - oryx/oryx_fantasy/wall9-6.png - - oryx/oryx_fantasy/wall9-7.png - - oryx/oryx_fantasy/wall9-8.png - - oryx/oryx_fantasy/wall9-9.png - - oryx/oryx_fantasy/wall9-10.png - - oryx/oryx_fantasy/wall9-11.png - - oryx/oryx_fantasy/wall9-12.png - - oryx/oryx_fantasy/wall9-13.png - - oryx/oryx_fantasy/wall9-14.png - - oryx/oryx_fantasy/wall9-15.png - Block2D: - - Color: [ 0.5, 0.5, 0.5 ] - Shape: square diff --git a/python/examples/rllib/repro/reproduce.py b/python/examples/rllib/repro/reproduce.py deleted file mode 100644 index a4050e000..000000000 --- a/python/examples/rllib/repro/reproduce.py +++ /dev/null @@ -1,35 +0,0 @@ - -import ray -from ray import tune -from ray.rllib.agents.ppo import PPOTrainer -from ray.rllib.examples.env.random_env import RandomEnv - -if __name__ == '__main__': - - ray.init(num_gpus=1) - - env_name = 'ray-ma-env' - - config = { - 'framework': 'torch', - 'num_workers': 10, - #'num_envs_per_worker': 1, - - #'use_gae': False, - - 'num_gpus': 1, - - 'env': RandomEnv, - - } - - stop = { - 'timesteps_total': 10000, - } - - trainer = PPOTrainer(config=config) - - for i in range(100): - trainer.train() - - #result = tune.run(PPOTrainer, config=config, stop=stop) \ No newline at end of file diff --git a/python/examples/rllib/rllib_multiagent_taggers.py b/python/examples/rllib/rllib_multi_agent.py similarity index 90% rename from python/examples/rllib/rllib_multiagent_taggers.py rename to python/examples/rllib/rllib_multi_agent.py index c3fcb7bce..9b95a75ec 100644 --- a/python/examples/rllib/rllib_multiagent_taggers.py +++ b/python/examples/rllib/rllib_multi_agent.py @@ -4,6 +4,7 @@ import ray from ray import tune from ray.rllib.agents.impala import ImpalaTrainer +from ray.rllib.agents.ppo import PPOTrainer from ray.rllib.models import ModelCatalog from ray.tune.registry import register_env @@ -15,7 +16,7 @@ sep = os.pathsep os.environ['PYTHONPATH'] = sep.join(sys.path) - ray.init(num_gpus=1) + #ray.init() env_name = 'ray-ma-env' @@ -36,6 +37,8 @@ def _create_env(env_config): 'num_workers': 8, 'num_envs_per_worker': 2, + 'num_gpus': 1, + 'model': { 'custom_model': 'SimpleConv', 'custom_model_config': {} @@ -51,7 +54,7 @@ def _create_env(env_config): }, 'random_level_on_reset': True, - 'yaml_file': 'foragers.yaml', + 'yaml_file': 'Multi-Agent/foragers.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'max_steps': 500, }, @@ -69,4 +72,4 @@ def _create_env(env_config): 'timesteps_total': max_training_steps, } - result = tune.run(ImpalaTrainer, config=config, stop=stop) + result = tune.run(PPOTrainer, config=config, stop=stop) From 9d500a00ca18cd99308cadc4f4252d8a1e3b7364 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Mon, 8 Mar 2021 17:37:26 +0000 Subject: [PATCH 29/34] minor test fix tweaks --- python/examples/griddlyrts/griddly_rts_p1.png | Bin 186 -> 219 bytes python/examples/griddlyrts/griddly_rts_p2.png | Bin 186 -> 223 bytes python/examples/rllib/rllib_multi_agent.py | 4 +- resources/games/Multi-Agent/foragers.yaml | 53 +++++++++++++++- resources/games/Multi-Agent/robot_tag_12.yaml | 2 +- resources/games/Multi-Agent/robot_tag_8.yaml | 2 +- tests/src/Griddly/Core/GameProcessTest.cpp | 58 +++++++++++------- 7 files changed, 89 insertions(+), 30 deletions(-) diff --git a/python/examples/griddlyrts/griddly_rts_p1.png b/python/examples/griddlyrts/griddly_rts_p1.png index 8bc6b1cddddb8014eb9f02a10006d607b8fbc1ef..42f1cd370078fa1ac8d31fa5f9383b1f82fe9301 100644 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901SGegx)utg7J0fjhE&{odu=1v0ROp&DaBt&PcY*xy~)yFL=om3TImJHH49 Px|qS!)z4*}Q$iB}x>QmW literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901SGegx)utg8a-VcLn>~)y(new MockLevelGenerator()); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); - auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + auto mockGDYFactoryPtr = std::shared_ptr(new MockGDYFactory()); EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) .Times(2); - EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + EXPECT_CALL(*mockGDYFactoryPtr, getLevelGenerator) .WillRepeatedly(Return(mockLevelGeneratorPtr)); - EXPECT_CALL(*mockGDYFactory, getPlayerCount) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerCount) .WillRepeatedly(Return(1)); - EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerObserverDefinition) .WillRepeatedly(Return(PlayerObserverDefinition{})); - EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + EXPECT_CALL(*mockGDYFactoryPtr, createTerminationHandler(Eq(mockGridPtr), _)) .WillRepeatedly(Return(mockTerminationHandlerPtr)); - EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + EXPECT_CALL(*mockGDYFactoryPtr, getGlobalVariableDefinitions()) .WillRepeatedly(Return(std::unordered_map{})); - auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactoryPtr, mockGridPtr)); auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); auto mockPlayerPtr = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); @@ -615,7 +615,11 @@ TEST(GameProcessTest, performActions) { ASSERT_EQ(result.reward, 14); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObserverPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGDYFactoryPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockLevelGeneratorPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockPlayerPtr.get())); } TEST(GameProcessTest, performActionsMultiAgentRewards) { @@ -638,23 +642,23 @@ TEST(GameProcessTest, performActionsMultiAgentRewards) { auto mockLevelGeneratorPtr = std::shared_ptr(new MockLevelGenerator()); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); - auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + auto mockGDYFactoryPtr = std::shared_ptr(new MockGDYFactory()); EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) .Times(2); - EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + EXPECT_CALL(*mockGDYFactoryPtr, getLevelGenerator) .WillRepeatedly(Return(mockLevelGeneratorPtr)); - EXPECT_CALL(*mockGDYFactory, getPlayerCount) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerCount) .WillRepeatedly(Return(3)); - EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerObserverDefinition) .WillRepeatedly(Return(PlayerObserverDefinition{})); - EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + EXPECT_CALL(*mockGDYFactoryPtr, createTerminationHandler(Eq(mockGridPtr), _)) .WillRepeatedly(Return(mockTerminationHandlerPtr)); - EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + EXPECT_CALL(*mockGDYFactoryPtr, getGlobalVariableDefinitions()) .WillRepeatedly(Return(std::unordered_map{})); - auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactoryPtr, mockGridPtr)); auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); auto mockPlayerPtr1 = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); @@ -708,7 +712,13 @@ TEST(GameProcessTest, performActionsMultiAgentRewards) { ASSERT_EQ(result3.reward, 15); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObserverPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGDYFactoryPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockLevelGeneratorPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockPlayerPtr1.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockPlayerPtr2.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockPlayerPtr3.get())); } TEST(GameProcessTest, performActionsDelayedReward) { @@ -727,23 +737,23 @@ TEST(GameProcessTest, performActionsDelayedReward) { auto mockLevelGeneratorPtr = std::shared_ptr(new MockLevelGenerator()); auto mockTerminationHandlerPtr = std::shared_ptr(new MockTerminationHandler(mockGridPtr)); - auto mockGDYFactory = std::shared_ptr(new MockGDYFactory()); + auto mockGDYFactoryPtr = std::shared_ptr(new MockGDYFactory()); EXPECT_CALL(*mockLevelGeneratorPtr, reset(Eq(mockGridPtr))) .Times(2); - EXPECT_CALL(*mockGDYFactory, getLevelGenerator) + EXPECT_CALL(*mockGDYFactoryPtr, getLevelGenerator) .WillRepeatedly(Return(mockLevelGeneratorPtr)); - EXPECT_CALL(*mockGDYFactory, getPlayerCount) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerCount) .WillRepeatedly(Return(1)); - EXPECT_CALL(*mockGDYFactory, getPlayerObserverDefinition) + EXPECT_CALL(*mockGDYFactoryPtr, getPlayerObserverDefinition) .WillRepeatedly(Return(PlayerObserverDefinition{})); - EXPECT_CALL(*mockGDYFactory, createTerminationHandler(Eq(mockGridPtr), _)) + EXPECT_CALL(*mockGDYFactoryPtr, createTerminationHandler(Eq(mockGridPtr), _)) .WillRepeatedly(Return(mockTerminationHandlerPtr)); - EXPECT_CALL(*mockGDYFactory, getGlobalVariableDefinitions()) + EXPECT_CALL(*mockGDYFactoryPtr, getGlobalVariableDefinitions()) .WillRepeatedly(Return(std::unordered_map{})); - auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactory, mockGridPtr)); + auto gameProcessPtr = std::shared_ptr(new TurnBasedGameProcess(ObserverType::NONE, mockGDYFactoryPtr, mockGridPtr)); auto mockObserverPtr = std::shared_ptr(new MockObserver(mockGridPtr)); auto mockPlayerPtr = mockPlayer("Bob", 1, gameProcessPtr, nullptr, mockObserverPtr); @@ -771,8 +781,12 @@ TEST(GameProcessTest, performActionsDelayedReward) { ASSERT_FALSE(result.terminated); ASSERT_EQ(result.reward, 19); - + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGridPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockPlayerPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockObserverPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockGDYFactoryPtr.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockLevelGeneratorPtr.get())); EXPECT_TRUE(Mock::VerifyAndClearExpectations(mockTerminationHandlerPtr.get())); } From db2169238406cba0f7172f92186203e02f30b9ed Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Mar 2021 08:49:54 +0000 Subject: [PATCH 30/34] fixing test where order can be different on different OS --- python/tests/step_test.py | 42 ++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/python/tests/step_test.py b/python/tests/step_test.py index 2b6530bd0..a170c8358 100644 --- a/python/tests/step_test.py +++ b/python/tests/step_test.py @@ -144,7 +144,6 @@ def test_step_SinglePlayer_SelectSource_SingleActionType_MultipleAction(test_nam assert env.action_space.shape == (3,) assert np.all(env.action_space.nvec == [5, 6, 5]) - assert env.game.get_object_names() == ['avatar2', 'avatar1'] obs, reward, done, info = env.step([ [2, 3, 1], @@ -162,6 +161,13 @@ def test_step_SinglePlayer_SelectSource_SingleActionType_MultipleAction(test_nam assert avatar1_state['Location'] == [1, 3] assert avatar2_state['Location'] == [2, 4] + object_names = env.game.get_object_names() + avartar1_id = object_names.index('avatar1') + avartar2_id = object_names.index('avatar2') + + assert obs[avartar1_id, 1, 3] == 1 + assert obs[avartar2_id, 2, 4] == 1 + sample = env.action_space.sample() assert sample.shape == (3,) @@ -270,8 +276,6 @@ def test_step_SinglePlayer_SelectSource_MultipleActionType_MultipleAction(test_n assert env.action_space.shape == (4,) assert np.all(env.action_space.nvec == [5, 6, 2, 5]) - assert env.game.get_object_names() == ['avatar2', 'avatar1'] - obs, reward, done, info = env.step([ [2, 3, 0, 1], [1, 4, 0, 1] @@ -287,6 +291,13 @@ def test_step_SinglePlayer_SelectSource_MultipleActionType_MultipleAction(test_n assert avatar1_state['Location'] == [1, 3] assert avatar2_state['Location'] == [0, 4] + object_names = env.game.get_object_names() + avartar1_id = object_names.index('avatar1') + avartar2_id = object_names.index('avatar2') + + assert obs[avartar1_id, 1, 3] == 1 + assert obs[avartar2_id, 0, 4] == 1 + obs, reward, done, info = env.step([ [1, 3, 1, 3], [0, 4, 1, 3] @@ -571,7 +582,6 @@ def test_step_MultiplePlayer_SelectSource_SingleActionType_MultipleAction(test_n assert len(env.action_space) == 2 assert env.global_observation_space.shape == (2, 5, 6) - assert env.game.get_object_names() == ['avatar2', 'avatar1'] for p in range(env.player_count): assert env.observation_space[p].shape == (2, 5, 6) @@ -601,12 +611,22 @@ def test_step_MultiplePlayer_SelectSource_SingleActionType_MultipleAction(test_n assert player1_avatar1_state['Location'] == [0, 3] assert player1_avatar2_state['Location'] == [4, 4] + object_names = env.game.get_object_names() + avartar1_id = object_names.index('avatar1') + avartar2_id = object_names.index('avatar2') + + assert obs[0][avartar1_id, 0, 3] == 1 + assert obs[0][avartar2_id, 4, 4] == 1 + player2_avatar1_state = get_object_state(env, 'avatar1', player=2) player2_avatar2_state = get_object_state(env, 'avatar2', player=2) assert player2_avatar1_state['Location'] == [2, 3] assert player2_avatar2_state['Location'] == [1, 4] + assert obs[0][avartar1_id, 2, 3] == 1 + assert obs[0][avartar2_id, 1, 4] == 1 + sample = env.action_space.sample() assert len(sample) == 2 assert sample[0].shape == (3,) @@ -636,7 +656,6 @@ def test_step_MultiplePlayer_SelectSource_MultipleActionType_MultipleAction(test assert len(env.action_space) == 2 assert env.global_observation_space.shape == (2, 5, 6) - assert env.game.get_object_names() == ['avatar2', 'avatar1'] for p in range(env.player_count): assert env.observation_space[p].shape == (2, 5, 6) @@ -666,12 +685,25 @@ def test_step_MultiplePlayer_SelectSource_MultipleActionType_MultipleAction(test assert player1_avatar1_state['Location'] == [0, 3] assert player1_avatar2_state['Location'] == [4, 4] + object_names = env.game.get_object_names() + avartar1_id = object_names.index('avatar1') + avartar2_id = object_names.index('avatar2') + + assert obs[0][avartar1_id, 0, 3] == 1 + assert obs[0][avartar2_id, 4, 4] == 1 + player2_avatar1_state = get_object_state(env, 'avatar1', player=2) player2_avatar2_state = get_object_state(env, 'avatar2', player=2) assert player2_avatar1_state['Location'] == [2, 3] assert player2_avatar2_state['Location'] == [1, 4] + avartar1_id = object_names.index('avatar1') + avartar2_id = object_names.index('avatar2') + + assert obs[1][avartar1_id, 2, 3] == 1 + assert obs[1][avartar2_id, 1, 4] == 1 + sample = env.action_space.sample() assert len(sample) == 2 assert sample[0].shape == (4,) From bda91058f8c0e40da9008c4329384f09d8b69829 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Mar 2021 15:58:25 +0000 Subject: [PATCH 31/34] finishing off some docs for rllib release --- .../GDY/Environment/BackgroundTile/index.rst | 17 ---- .../Observers/Isometric/TileOffsetY/index.rst | 17 ---- .../GDY/Environment/Player/Mode/index.rst | 21 ----- .../GDY/Environment/TileSize/index.rst | 19 ---- docs/rllib/intro/index.rst | 90 +++++++++++++++++-- docs/rllib/multi-agent/index.rst | 2 + docs/rllib/single-agent/index.rst | 12 +-- 7 files changed, 94 insertions(+), 84 deletions(-) delete mode 100644 docs/reference/GDY/Environment/BackgroundTile/index.rst delete mode 100644 docs/reference/GDY/Environment/Observers/Isometric/TileOffsetY/index.rst delete mode 100644 docs/reference/GDY/Environment/Player/Mode/index.rst delete mode 100644 docs/reference/GDY/Environment/TileSize/index.rst diff --git a/docs/reference/GDY/Environment/BackgroundTile/index.rst b/docs/reference/GDY/Environment/BackgroundTile/index.rst deleted file mode 100644 index 8b770587e..000000000 --- a/docs/reference/GDY/Environment/BackgroundTile/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _#/properties/Environment/properties/BackgroundTile: - -.. #/properties/Environment/properties/BackgroundTile - -Background Tile -=============== - -:Description: Filename of an the image that will be used as a background tile - -.. list-table:: - - * - **Data Type** - - **YAML Key** - * - string - - ``BackgroundTile`` - - diff --git a/docs/reference/GDY/Environment/Observers/Isometric/TileOffsetY/index.rst b/docs/reference/GDY/Environment/Observers/Isometric/TileOffsetY/index.rst deleted file mode 100644 index 3fe3c4c34..000000000 --- a/docs/reference/GDY/Environment/Observers/Isometric/TileOffsetY/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _#/properties/Environment/properties/Observers/properties/Isometric/properties/TileOffsetY: - -.. #/properties/Environment/properties/Observers/properties/Isometric/properties/TileOffsetY - -Tile Y Offset -============= - -:Description: If the center of isometric tiles is not the center of the images. The Y offset can be used to render the tiles correctly. - -.. list-table:: - - * - **Data Type** - - **YAML Key** - * - integer - - ``TileOffsetY`` - - diff --git a/docs/reference/GDY/Environment/Player/Mode/index.rst b/docs/reference/GDY/Environment/Player/Mode/index.rst deleted file mode 100644 index 1988e7957..000000000 --- a/docs/reference/GDY/Environment/Player/Mode/index.rst +++ /dev/null @@ -1,21 +0,0 @@ -.. _#/properties/Environment/properties/Player/properties/Mode: - -.. #/properties/Environment/properties/Player/properties/Mode - -Player Mode -=========== - -:Description: The player mode defines how the Griddly engine will handle different players. - -.. list-table:: - - * - **Data Type** - - **YAML Key** - - **Allowed Values** - - **Default Value** - * - string - - ``Mode`` - - ``SINGLE``, ``RTS``, ``MULTI`` - - ``MULTI`` - - diff --git a/docs/reference/GDY/Environment/TileSize/index.rst b/docs/reference/GDY/Environment/TileSize/index.rst deleted file mode 100644 index 284362153..000000000 --- a/docs/reference/GDY/Environment/TileSize/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _#/properties/Environment/properties/TileSize: - -.. #/properties/Environment/properties/TileSize - -TileSize -======== - -:Description: The common size of all the tiles in the environment. If tile images are supplied with different sizes to this, they will be scaled to this size - -.. list-table:: - - * - **Data Type** - - **YAML Key** - - **Default Value** - * - integer - - ``TileSize`` - - ``10`` - - diff --git a/docs/rllib/intro/index.rst b/docs/rllib/intro/index.rst index 312806b69..698cd381b 100644 --- a/docs/rllib/intro/index.rst +++ b/docs/rllib/intro/index.rst @@ -13,6 +13,24 @@ Griddly provides two classes, ``RLLibEnv`` and ``RLLibMultiAgentWrapper`` which Examples for :ref:`single-agent ` and :ref:`multi-agent ` training are provided. +.. warning:: All examples and networks are implemented using PyTorch. Some examples may be modified to work with Tensorflow, but we do not provide explicit support for Tensorflow. + + +************** +Examples Setup +************** + +Griddly installs most of the dependencies for you automatically when it is installed, however you will need to install RLLlib and Pytorch to run the provided examples. + +You can install RLLib and pytorch using the following command: + +.. code-block:: bash + + pip install ray[rllib]==1.2.0 torch==1.8.0 + + +All RLLib examples can be found in ``python/examples/rllib/`` + ********************** Environment Parameters @@ -72,16 +90,20 @@ We provide a few custom agent models that can be used with any Griddly environme Simple Convolutional agent ========================== +The simple convolutional agent stacks three convolutional layers that preserve the size of the input. After these layers the representation is flattened and linear layers are then used for the actor and critic heads. + +To use the simple ``SimpleConvAgent, register the custom model with RLLib and then use it in your training ``config``: + .. code-block:: python - ModelCatalog.register_custom_model('GAP', GAPAgent) + ModelCatalog.register_custom_model('SimpleConv', SimpleConvAgent) ... config = { 'model': { - 'custom_model': 'GAP' + 'custom_model': 'SimpleConv' 'custom_model_config': ..... } @@ -89,6 +111,65 @@ Simple Convolutional agent } +SimpleConvAgent +--------------- + +.. code-block:: + + class SimpleConvAgent(TorchModelV2, nn.Module): + """ + Simple Convolution agent that calculates the required linear output layer + """ + + def __init__(self, obs_space, action_space, num_outputs, model_config, name): + super().__init__(obs_space, action_space, num_outputs, model_config, name) + nn.Module.__init__(self) + + self._num_objects = obs_space.shape[2] + self._num_actions = num_outputs + + linear_flatten = np.prod(obs_space.shape[:2])*64 + + self.network = nn.Sequential( + layer_init(nn.Conv2d(self._num_objects, 32, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(32, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + layer_init(nn.Conv2d(64, 64, 3, padding=1)), + nn.ReLU(), + nn.Flatten(), + layer_init(nn.Linear(linear_flatten, 1024)), + nn.ReLU(), + layer_init(nn.Linear(1024, 512)), + nn.ReLU(), + layer_init(nn.Linear(512, 512)) + ) + + self._actor_head = nn.Sequential( + layer_init(nn.Linear(512, 512), std=0.01), + nn.ReLU(), + layer_init(nn.Linear(512, self._num_actions), std=0.01) + ) + + self._critic_head = nn.Sequential( + layer_init(nn.Linear(512, 1), std=0.01) + ) + + def forward(self, input_dict, state, seq_lens): + obs_transformed = input_dict['obs'].permute(0, 3, 1, 2) + network_output = self.network(obs_transformed) + value = self._critic_head(network_output) + self._value = value.reshape(-1) + logits = self._actor_head(network_output) + return logits, state + + def value_function(self): + return self._value + + + .. _gap_agent: Global Average Pooling @@ -115,9 +196,8 @@ All you need to do is register the custom model with RLLib and then use it in yo } - - -The implementation of the Global Average Pooling agent is essentially a stack of convolutions that maintain the shape of the state, +GAPAgent +-------- .. code-block:: python diff --git a/docs/rllib/multi-agent/index.rst b/docs/rllib/multi-agent/index.rst index c9e93ee9c..0d1b61a7a 100644 --- a/docs/rllib/multi-agent/index.rst +++ b/docs/rllib/multi-agent/index.rst @@ -40,6 +40,8 @@ The Foragers environment as seen from the "Global Observer" view. The following code will run the Foragers example for 10M steps using IMPALA to train. +.. seealso:: To use a different game, or specific level, just change the ``yaml_file`` or set a ``level`` parameter in the ``env_config``. Other options can be found :ref:`here ` + .. code-block:: python import os diff --git a/docs/rllib/single-agent/index.rst b/docs/rllib/single-agent/index.rst index 1125901c3..a9c2bc40b 100644 --- a/docs/rllib/single-agent/index.rst +++ b/docs/rllib/single-agent/index.rst @@ -11,23 +11,25 @@ The Griddly RLLibEnv wrapper allows any of the single-agent games to be trained register_env('my-single-agent-environment', RLlibEnv) + +************ +Full Example +************ + The example below uses IMPALA to train on the :ref:`Partially Observable Clusters ` Environment. The agent in the :ref:`Partially Observable Clusters ` environment has a 5x5 partially observable ego-centric view. By default the agent sees a :ref:`VECTOR ` view of the environment. This view is passed to a :ref:`Global Average Pooling Agent ` to produce the policy. +.. seealso:: To use a different game, or specific level, just change the ``yaml_file`` or set a ``level`` parameter in the ``env_config``. Other options can be found :ref:`here ` + .. figure:: img/Partially_Observable_Clusters-level-Sprite2D-1.png :align: center The Clusters environment as seen from the "Global Observer" view. - -************ -Full Example -************ - .. code-block:: python import os From cb5a30811746cca44253971f4c673b341d4c4416 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Mar 2021 16:33:56 +0000 Subject: [PATCH 32/34] few more line-ups with docs --- python/examples/rllib/requirements.txt | 2 +- python/examples/rllib/rllib_multi_agent.py | 3 ++- python/examples/rllib/rllib_single_agent.py | 2 +- python/griddly/util/rllib/torch/agents/conv_agent.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/python/examples/rllib/requirements.txt b/python/examples/rllib/requirements.txt index 7e7d9d770..90f045abd 100644 --- a/python/examples/rllib/requirements.txt +++ b/python/examples/rllib/requirements.txt @@ -1,2 +1,2 @@ ray[rllib] >= 1.2.0 -torch >= 1.7.1 \ No newline at end of file +torch >= 1.8.0 \ No newline at end of file diff --git a/python/examples/rllib/rllib_multi_agent.py b/python/examples/rllib/rllib_multi_agent.py index 48444f8e0..0b80a9ca3 100644 --- a/python/examples/rllib/rllib_multi_agent.py +++ b/python/examples/rllib/rllib_multi_agent.py @@ -3,6 +3,7 @@ import ray from ray import tune +from ray.rllib.agents.impala import ImpalaTrainer from ray.rllib.agents.ppo import PPOTrainer from ray.rllib.models import ModelCatalog from ray.tune.registry import register_env @@ -70,4 +71,4 @@ def _create_env(env_config): 'timesteps_total': max_training_steps, } - result = tune.run(PPOTrainer, config=config, stop=stop) + result = tune.run(ImpalaTrainer, config=config, stop=stop) diff --git a/python/examples/rllib/rllib_single_agent.py b/python/examples/rllib/rllib_single_agent.py index 4cabc969f..c9a603505 100644 --- a/python/examples/rllib/rllib_single_agent.py +++ b/python/examples/rllib/rllib_single_agent.py @@ -40,7 +40,7 @@ }, 'random_level_on_reset': True, - 'yaml_file': 'Single-Player/GVGAI/cookmepasta_partially_observable.yaml', + 'yaml_file': 'Single-Player/GVGAI/clusters_partially_observable.yaml', 'global_observer_type': gd.ObserverType.SPRITE_2D, 'max_steps': 1000, }, diff --git a/python/griddly/util/rllib/torch/agents/conv_agent.py b/python/griddly/util/rllib/torch/agents/conv_agent.py index 8af68ed33..52dccf80a 100644 --- a/python/griddly/util/rllib/torch/agents/conv_agent.py +++ b/python/griddly/util/rllib/torch/agents/conv_agent.py @@ -6,7 +6,7 @@ class SimpleConvAgent(TorchModelV2, nn.Module): """ - Smiple Convolution agent that calculates the required linear output layer + Simple Convolution agent that calculates the required linear output layer """ def __init__(self, obs_space, action_space, num_outputs, model_config, name): From 5662f61c3bc497bc2554fb80bf77a303bd91d88e Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Mar 2021 17:12:43 +0000 Subject: [PATCH 33/34] fixing warning in butterflies game --- resources/games/Single-Player/GVGAI/random_butterflies.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/games/Single-Player/GVGAI/random_butterflies.yaml b/resources/games/Single-Player/GVGAI/random_butterflies.yaml index 305ddcc79..ab3dbe7e2 100644 --- a/resources/games/Single-Player/GVGAI/random_butterflies.yaml +++ b/resources/games/Single-Player/GVGAI/random_butterflies.yaml @@ -271,7 +271,7 @@ Actions: Object: butterfly Commands: - remove: true - - reward: -1 + # if the spider moves into the catcher it dies - Src: Object: spider From 7d96fcfba90e110810aed57a2c03b9f209937ea2 Mon Sep 17 00:00:00 2001 From: Bam4d Date: Tue, 9 Mar 2021 17:14:40 +0000 Subject: [PATCH 34/34] bumping version numberinos --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- CMakeLists.txt | 2 +- bindings/python.cpp | 2 +- docs/conf.py | 2 +- python/setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 008339d4b..0cd31c1d4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. 0.3.3] + - Version [e.g. 1.0.0] **Additional context** Add any other context about the problem here. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e9b38f4c..270aa3a82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10.0) -project(Griddly VERSION 0.3.3) +project(Griddly VERSION 1.0.0) set(BINARY ${CMAKE_PROJECT_NAME}) diff --git a/bindings/python.cpp b/bindings/python.cpp index 49f05746f..ce84016ff 100644 --- a/bindings/python.cpp +++ b/bindings/python.cpp @@ -12,7 +12,7 @@ namespace griddly { PYBIND11_MODULE(python_griddly, m) { m.doc() = "Griddly python bindings"; - m.attr("version") = "0.3.3"; + m.attr("version") = "1.0.0"; #ifndef NDEBUG spdlog::set_level(spdlog::level::debug); diff --git a/docs/conf.py b/docs/conf.py index 27fd3bbf1..47a2bb244 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = 'Chris Bamford' # The full version, including alpha/beta/rc tags -release = '0.3.3' +release = '1.0.0' # -- General configuration --------------------------------------------------- diff --git a/python/setup.py b/python/setup.py index 9dbe0bd96..430fb88d1 100644 --- a/python/setup.py +++ b/python/setup.py @@ -71,7 +71,7 @@ def griddly_package_data(config='Debug'): setup( name='griddly', - version="0.3.3", + version="1.0.0", author_email="chrisbam4d@gmail.com", description="Griddly Python Libraries", long_description=long_description,