Skip to content

Commit

Permalink
Expose RGB as an observation and action space to the environment. Res…
Browse files Browse the repository at this point in the history
…olution is no longer optional. The feature layer and rgb resolutions can be chosen independently. If you ask for both you must specify which is your action space.

PiperOrigin-RevId: 176098769
  • Loading branch information
tewalds committed Jan 11, 2018
1 parent fa4fb11 commit 949a3a8
Show file tree
Hide file tree
Showing 10 changed files with 490 additions and 122 deletions.
27 changes: 17 additions & 10 deletions docs/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ passing a screen coordinate to an action:

```python
# Spatial observations have the y-coordinate first:
y, x = (obs.observation["screen"][_PLAYER_RELATIVE] == _PLAYER_NEUTRAL).nonzero()
y, x = (obs.observation["feature_screen"][_PLAYER_RELATIVE] == _PLAYER_NEUTRAL).nonzero()

# Actions expect x-coordinate first:
target = [int(x.mean()), int(y.mean())]
Expand All @@ -166,12 +166,18 @@ passing a screen coordinate to an action:

##### RGB Pixels

This is still work in progress but will be available in the future.
RGB pixels are available for both the main screen area as well as for the
minimap at a resolution of your choice. This uses the same perspective camera as
a human would see, but doesn't include all the extra chrome around the screen
like the command card, selection box, build queue, etc. They are exposed as
`rgb_screen` and `rgb_minimap`.

##### Feature layers

Instead of normal RGB pixels, the game exposes feature layers. There are ~20 of
them broken down between the screen and minimap.
The game also exposes feature layers. They represent roughly the same
information as RGB pixels except that the information is decomposed and
structured. There are ~20 feature layers broken down between the screen and
minimap and exposed as `feature_screen` and `feature_minimap`.

The full list is defined in `pysc2.lib.features`.

Expand Down Expand Up @@ -431,16 +437,17 @@ actions:

## RL Environment

The main SC2 environment is at `pysc2.env.sc2_env`, with the action and observation
space defined in `pysc2.lib.features`.
The main SC2 environment is at `pysc2.env.sc2_env`, with the action and
observation space defined in `pysc2.lib.features`.

The most important argument is `map_name`, which is how to find the map.
Find the names by using `pysc2.bin.map_list` or by looking in `pysc2/maps/*.py`.

`screen_size_px` and `minimap_size_px` let you specify the resolution of the
features layers you get in the observations. Higher resolution obviously gives
higher location precision, at the cost of larger observations as well as a
larger action space.
`feature_screen_size`, `feature_minimap_size`, `rgb_screen_size`,
`rgb_minimap_size`, and the `_width` and `_height` variants let you specify the
resolution of the spatial observations. Higher resolution obviously gives higher
location precision, at the cost of larger observations as well as a larger
action space.

`step_mul` let's you skip observations and actions. For example a `step_mul` of
16 means that the environment gets stepped forward 16 times in between the
Expand Down
6 changes: 3 additions & 3 deletions pysc2/agents/scripted_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class MoveToBeacon(base_agent.BaseAgent):
def step(self, obs):
super(MoveToBeacon, self).step(obs)
if _MOVE_SCREEN in obs.observation["available_actions"]:
player_relative = obs.observation["screen"][_PLAYER_RELATIVE]
player_relative = obs.observation["feature_screen"][_PLAYER_RELATIVE]
neutral_y, neutral_x = (player_relative == _PLAYER_NEUTRAL).nonzero()
if not neutral_y.any():
return actions.FunctionCall(_NO_OP, [])
Expand All @@ -57,7 +57,7 @@ class CollectMineralShards(base_agent.BaseAgent):
def step(self, obs):
super(CollectMineralShards, self).step(obs)
if _MOVE_SCREEN in obs.observation["available_actions"]:
player_relative = obs.observation["screen"][_PLAYER_RELATIVE]
player_relative = obs.observation["feature_screen"][_PLAYER_RELATIVE]
neutral_y, neutral_x = (player_relative == _PLAYER_NEUTRAL).nonzero()
player_y, player_x = (player_relative == _PLAYER_FRIENDLY).nonzero()
if not neutral_y.any() or not player_y.any():
Expand All @@ -79,7 +79,7 @@ class DefeatRoaches(base_agent.BaseAgent):
def step(self, obs):
super(DefeatRoaches, self).step(obs)
if _ATTACK_SCREEN in obs.observation["available_actions"]:
player_relative = obs.observation["screen"][_PLAYER_RELATIVE]
player_relative = obs.observation["feature_screen"][_PLAYER_RELATIVE]
roach_y, roach_x = (player_relative == _PLAYER_HOSTILE).nonzero()
if not roach_y.any():
return actions.FunctionCall(_NO_OP, [])
Expand Down
96 changes: 82 additions & 14 deletions pysc2/env/sc2_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from pysc2 import maps
from pysc2 import run_configs
from pysc2.env import environment
from pysc2.lib import actions as actions_lib
from pysc2.lib import features
from pysc2.lib import point
from pysc2.lib import renderer_human
from pysc2.lib import run_parallel
from pysc2.lib import stopwatch
Expand Down Expand Up @@ -61,6 +61,9 @@
"A": sc_pb.CheatInsane,
}

# Re-export this enum to make it easy to construct the environment.
ActionSpace = actions_lib.ActionSpace # pylint: disable=invalid-name


class SC2Env(environment.Base):
"""A Starcraft II environment.
Expand All @@ -78,13 +81,35 @@ def __init__(self, # pylint: disable=invalid-name
# pylint: disable=g-doc-args
"""Create a SC2 Env.
You must pass a resolution that you want to play at. You can send either
feature layer resolution or rgb resolution or both. If you send both you
must also choose which to use as your action space. Regardless of which you
choose you must send both the screen and minimap resolutions.
For each of the 4 resolutions, either specify size or both width and
height. If you specify size then both width and height will take that value.
Args:
_only_use_kwargs: Don't pass args, only kwargs.
map_name: Name of a SC2 map. Run bin/map_list to get the full list of
known maps. Alternatively, pass a Map instance. Take a look at the
docs in maps/README.md for more information on available maps.
screen_size_px: The size of your screen output in pixels.
minimap_size_px: The size of your minimap output in pixels.
feature_screen_size: Sets feature_screen_width and feature_screen_width.
feature_screen_width: The width of the feature layer screen observation.
feature_screen_height: The height of the feature layer screen observation.
feature_minimap_size: Sets feature_minimap_width and
feature_minimap_height.
feature_minimap_width: The width of the feature layer minimap observation.
feature_minimap_height: The height of the feature layer minimap
observation.
rgb_screen_size: Sets rgb_screen_width and rgb_screen_height.
rgb_screen_width: The width of the rgb screen observation.
rgb_screen_height: The height of the rgb screen observation.
rgb_minimap_size: Sets rgb_minimap_width and rgb_minimap_height.
rgb_minimap_width: The width of the rgb minimap observation.
rgb_minimap_height: The height of the rgb minimap observation.
action_space: If you pass both feature and rgb sizes, then you must also
specify which you want to use for your actions as an ActionSpace enum.
camera_width_world_units: The width of your screen in world units. If your
screen_size_px=(64, 48) and camera_width is 24, then each px
represents 24 / 64 = 0.375 world units in each of x and y. It'll then
Expand Down Expand Up @@ -112,6 +137,7 @@ def __init__(self, # pylint: disable=invalid-name
Raises:
ValueError: if the agent_race, bot_race or difficulty are invalid.
ValueError: if the resolutions aren't specified correctly.
"""
# pylint: enable=g-doc-args
if _only_use_kwargs:
Expand All @@ -136,8 +162,21 @@ def __init__(self, # pylint: disable=invalid-name
def _setup(self,
player_setup,
map_name,
screen_size_px=(64, 64),
minimap_size_px=(64, 64),
screen_size_px=None, # deprecated
minimap_size_px=None, # deprecated
feature_screen_size=None,
feature_screen_width=None,
feature_screen_height=None,
feature_minimap_size=None,
feature_minimap_width=None,
feature_minimap_height=None,
rgb_screen_size=None,
rgb_screen_width=None,
rgb_screen_height=None,
rgb_minimap_size=None,
rgb_minimap_width=None,
rgb_minimap_height=None,
action_space=None,
camera_width_world_units=None,
discount=1.,
visualize=False,
Expand All @@ -149,6 +188,29 @@ def _setup(self,
score_multiplier=None,
random_seed=None):

if screen_size_px or minimap_size_px:
raise DeprecationWarning(
"screen_size_px and minimap_size_px are deprecated. Use the feature "
"or rgb variants instead. Make sure to check your observations too "
"since they also switched from screen/minimap to feature and rgb "
"variants.")

feature_screen_px = features.point_from_size_width_height(
feature_screen_size, feature_screen_width, feature_screen_height)
feature_minimap_px = features.point_from_size_width_height(
feature_minimap_size, feature_minimap_width, feature_minimap_height)
rgb_screen_px = features.point_from_size_width_height(
rgb_screen_size, rgb_screen_width, rgb_screen_height)
rgb_minimap_px = features.point_from_size_width_height(
rgb_minimap_size, rgb_minimap_width, rgb_minimap_height)

if bool(feature_screen_px) != bool(feature_minimap_px):
raise ValueError("Must set all the feature layer sizes.")
if bool(rgb_screen_px) != bool(rgb_minimap_px):
raise ValueError("Must set all the rgb sizes.")
if not feature_screen_px and not rgb_screen_px:
raise ValueError("Must set either the feature layer or rgb sizes.")

if save_replay_episodes and not replay_dir:
raise ValueError("Missing replay_dir")

Expand Down Expand Up @@ -178,21 +240,27 @@ def _setup(self,
self._run_config = run_configs.get()
self._parallel = run_parallel.RunParallel() # Needed for multiplayer.

screen_size_px = point.Point(*screen_size_px)
minimap_size_px = point.Point(*minimap_size_px)
interface = sc_pb.InterfaceOptions(
raw=visualize, score=True,
feature_layer=sc_pb.SpatialCameraSetup(
width=camera_width_world_units or 24))
screen_size_px.assign_to(interface.feature_layer.resolution)
minimap_size_px.assign_to(interface.feature_layer.minimap_resolution)
interface = sc_pb.InterfaceOptions(raw=visualize, score=True)
if feature_screen_px:
interface.feature_layer.width = camera_width_world_units or 24
feature_screen_px.assign_to(interface.feature_layer.resolution)
feature_minimap_px.assign_to(interface.feature_layer.minimap_resolution)
if rgb_screen_px:
rgb_screen_px.assign_to(interface.render.resolution)
rgb_minimap_px.assign_to(interface.render.minimap_resolution)

self._launch(interface, player_setup)

game_info = self._controllers[0].game_info()
static_data = self._controllers[0].data()

self._features = features.Features(game_info)
if game_info.options != interface:
logging.warning(
"Actual interface options don't match requested options:\n"
"Requested:\n%s\n\nActual:\n%s", game_info.options, interface)

self._features = features.Features(game_info=game_info,
action_space=action_space)
if visualize:
self._renderer_human = renderer_human.RendererHuman()
self._renderer_human.init(game_info, static_data)
Expand Down
71 changes: 48 additions & 23 deletions pysc2/lib/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,108 +20,133 @@
import collections
import numbers

import enum
import six
from pysc2.lib import point

from s2clientprotocol import spatial_pb2 as sc_spatial
from s2clientprotocol import ui_pb2 as sc_ui


def no_op(action):
del action
class ActionSpace(enum.Enum):
FEATURES = 1
RGB = 2


def move_camera(action, minimap):
def spatial(action, action_space):
"""Choose the action space for the action proto."""
if action_space == ActionSpace.FEATURES:
return action.action_feature_layer
elif action_space == ActionSpace.RGB:
return action.action_render
else:
raise ValueError("Unexpected value for action_space: %s" % action_space)


def no_op(action, action_space):
del action, action_space


def move_camera(action, action_space, minimap):
"""Move the camera."""
minimap.assign_to(action.action_feature_layer.camera_move.center_minimap)
minimap.assign_to(spatial(action, action_space).camera_move.center_minimap)


def select_point(action, select_point_act, screen):
def select_point(action, action_space, select_point_act, screen):
"""Select a unit at a point."""
select = action.action_feature_layer.unit_selection_point
select = spatial(action, action_space).unit_selection_point
screen.assign_to(select.selection_screen_coord)
select.type = select_point_act


def select_rect(action, select_add, screen, screen2):
def select_rect(action, action_space, select_add, screen, screen2):
"""Select units within a rectangle."""
select = action.action_feature_layer.unit_selection_rect
select = spatial(action, action_space).unit_selection_rect
out_rect = select.selection_screen_coord.add()
screen_rect = point.Rect(screen, screen2)
screen_rect.tl.assign_to(out_rect.p0)
screen_rect.br.assign_to(out_rect.p1)
select.selection_add = bool(select_add)


def select_idle_worker(action, select_worker):
def select_idle_worker(action, action_space, select_worker):
"""Select an idle worker."""
del action_space
action.action_ui.select_idle_worker.type = select_worker


def select_army(action, select_add):
def select_army(action, action_space, select_add):
"""Select the entire army."""
del action_space
action.action_ui.select_army.selection_add = select_add


def select_warp_gates(action, select_add):
def select_warp_gates(action, action_space, select_add):
"""Select all warp gates."""
del action_space
action.action_ui.select_warp_gates.selection_add = select_add


def select_larva(action):
def select_larva(action, action_space):
"""Select all larva."""
del action_space
action.action_ui.select_larva.SetInParent() # Adds the empty proto field.


def select_unit(action, select_unit_act, select_unit_id):
def select_unit(action, action_space, select_unit_act, select_unit_id):
"""Select a specific unit from the multi-unit selection."""
del action_space
select = action.action_ui.multi_panel
select.type = select_unit_act
select.unit_index = select_unit_id


def control_group(action, control_group_act, control_group_id):
def control_group(action, action_space, control_group_act, control_group_id):
"""Act on a control group, selecting, setting, etc."""
del action_space
select = action.action_ui.control_group
select.action = control_group_act
select.control_group_index = control_group_id


def unload(action, unload_id):
def unload(action, action_space, unload_id):
"""Unload a unit from a transport/bunker/nydus/etc."""
del action_space
action.action_ui.cargo_panel.unit_index = unload_id


def build_queue(action, build_queue_id):
def build_queue(action, action_space, build_queue_id):
"""Cancel a unit in the build queue."""
del action_space
action.action_ui.production_panel.unit_index = build_queue_id


def cmd_quick(action, ability_id, queued):
def cmd_quick(action, action_space, ability_id, queued):
"""Do a quick command like 'Stop' or 'Stim'."""
action_cmd = action.action_feature_layer.unit_command
action_cmd = spatial(action, action_space).unit_command
action_cmd.ability_id = ability_id
action_cmd.queue_command = queued


def cmd_screen(action, ability_id, queued, screen):
def cmd_screen(action, action_space, ability_id, queued, screen):
"""Do a command that needs a point on the screen."""
action_cmd = action.action_feature_layer.unit_command
action_cmd = spatial(action, action_space).unit_command
action_cmd.ability_id = ability_id
action_cmd.queue_command = queued
screen.assign_to(action_cmd.target_screen_coord)


def cmd_minimap(action, ability_id, queued, minimap):
def cmd_minimap(action, action_space, ability_id, queued, minimap):
"""Do a command that needs a point on the minimap."""
action_cmd = action.action_feature_layer.unit_command
action_cmd = spatial(action, action_space).unit_command
action_cmd.ability_id = ability_id
action_cmd.queue_command = queued
minimap.assign_to(action_cmd.target_minimap_coord)


def autocast(action, ability_id):
def autocast(action, action_space, ability_id):
"""Toggle autocast."""
del action_space
action.action_ui.toggle_autocast.ability_id = ability_id


Expand Down
Loading

0 comments on commit 949a3a8

Please sign in to comment.