Repository: google-deepmind/pysc2
Branch: master
Commit: 0df53d38c153
Files: 247
Total size: 25.5 MB
Directory structure:
gitextract_2o40zztk/
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── WORKSPACE
├── bazel/
│ ├── BUILD
│ ├── BUILD.dm_env
│ ├── BUILD.dm_env_rpc
│ ├── BUILD.s2protocol
│ ├── create_external_repos.bzl
│ ├── protobuf.patch
│ ├── requirements.txt
│ ├── s2clientprotocol.patch
│ └── setup_external_repos.bzl
├── docs/
│ ├── bazel.md
│ ├── converters.md
│ ├── environment.md
│ ├── maps.md
│ └── mini_games.md
├── pysc2/
│ ├── BUILD
│ ├── __init__.py
│ ├── agents/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── base_agent.py
│ │ ├── no_op_agent.py
│ │ ├── random_agent.py
│ │ └── scripted_agent.py
│ ├── bin/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── agent.py
│ │ ├── agent_remote.py
│ │ ├── battle_net_maps.py
│ │ ├── benchmark_observe.py
│ │ ├── benchmark_replay.py
│ │ ├── check_apm.py
│ │ ├── compare_binaries.py
│ │ ├── gen_actions.py
│ │ ├── gen_data.py
│ │ ├── gen_versions.py
│ │ ├── map_list.py
│ │ ├── mem_leak_check.py
│ │ ├── play.py
│ │ ├── play_vs_agent.py
│ │ ├── reencode_replays.py
│ │ ├── replay_actions.py
│ │ ├── replay_info.py
│ │ ├── replay_version.py
│ │ ├── run_tests.py
│ │ ├── update_battle_net_cache.py
│ │ └── valid_actions.py
│ ├── build_defs.bzl
│ ├── env/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── available_actions_printer.py
│ │ ├── base_env_wrapper.py
│ │ ├── converted_env.py
│ │ ├── converted_env_test.py
│ │ ├── converter/
│ │ │ ├── BUILD
│ │ │ ├── __init__.py
│ │ │ ├── cc/
│ │ │ │ ├── BUILD
│ │ │ │ ├── __init__.py
│ │ │ │ ├── castops.h
│ │ │ │ ├── check_protos_equal.h
│ │ │ │ ├── convert_obs.cc
│ │ │ │ ├── convert_obs.h
│ │ │ │ ├── convert_obs_test.cc
│ │ │ │ ├── converter.cc
│ │ │ │ ├── converter.h
│ │ │ │ ├── converter_test.cc
│ │ │ │ ├── encode_image_data.h
│ │ │ │ ├── features.cc
│ │ │ │ ├── features.h
│ │ │ │ ├── file_util.cc
│ │ │ │ ├── file_util.h
│ │ │ │ ├── game_data/
│ │ │ │ │ ├── BUILD
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── proto/
│ │ │ │ │ │ ├── BUILD
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── buffs.proto
│ │ │ │ │ │ ├── units.proto
│ │ │ │ │ │ └── upgrades.proto
│ │ │ │ │ ├── python/
│ │ │ │ │ │ ├── BUILD
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── uint8_lookup.cc
│ │ │ │ │ │ └── uint8_lookup_test.py
│ │ │ │ │ ├── raw_actions.cc
│ │ │ │ │ ├── raw_actions.h
│ │ │ │ │ ├── uint8_lookup.cc
│ │ │ │ │ ├── uint8_lookup.h
│ │ │ │ │ ├── visual_actions.cc
│ │ │ │ │ └── visual_actions.h
│ │ │ │ ├── general_order_ids.cc
│ │ │ │ ├── general_order_ids.h
│ │ │ │ ├── map_util.cc
│ │ │ │ ├── map_util.h
│ │ │ │ ├── python/
│ │ │ │ │ ├── BUILD
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── converter.cc
│ │ │ │ ├── raw_actions_encoder.cc
│ │ │ │ ├── raw_actions_encoder.h
│ │ │ │ ├── raw_actions_encoder_test.cc
│ │ │ │ ├── raw_camera.cc
│ │ │ │ ├── raw_camera.h
│ │ │ │ ├── raw_converter.cc
│ │ │ │ ├── raw_converter.h
│ │ │ │ ├── tensor_util.cc
│ │ │ │ ├── tensor_util.h
│ │ │ │ ├── test_data/
│ │ │ │ │ ├── BUILD
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── actions/
│ │ │ │ │ │ ├── feature_camera_move.pbtxt
│ │ │ │ │ │ ├── feature_unit_command.pbtxt
│ │ │ │ │ │ ├── feature_unit_selection_point.pbtxt
│ │ │ │ │ │ ├── ui_control_group_append.pbtxt
│ │ │ │ │ │ └── ui_control_group_recall.pbtxt
│ │ │ │ │ ├── obs_data1.pbtxt
│ │ │ │ │ └── recordings/
│ │ │ │ │ └── tvt_trunk.pb
│ │ │ │ ├── unit_lookups.cc
│ │ │ │ ├── unit_lookups.h
│ │ │ │ ├── unit_lookups_test.cc
│ │ │ │ ├── visual_actions.cc
│ │ │ │ ├── visual_actions.h
│ │ │ │ ├── visual_actions_test.cc
│ │ │ │ ├── visual_converter.cc
│ │ │ │ ├── visual_converter.h
│ │ │ │ └── visual_converter_test.cc
│ │ │ ├── converter.py
│ │ │ ├── converter_test.py
│ │ │ ├── derive_interface_options.py
│ │ │ └── proto/
│ │ │ ├── BUILD
│ │ │ ├── __init__.py
│ │ │ └── converter.proto
│ │ ├── enums.py
│ │ ├── environment.py
│ │ ├── host_remote_agent.py
│ │ ├── lan_sc2_env.py
│ │ ├── mock_sc2_env.py
│ │ ├── mock_sc2_env_comparison_test.py
│ │ ├── mock_sc2_env_test.py
│ │ ├── remote_sc2_env.py
│ │ ├── run_loop.py
│ │ ├── sc2_env.py
│ │ └── sc2_env_test.py
│ ├── lib/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── actions.py
│ │ ├── buffs.py
│ │ ├── colors.py
│ │ ├── features.py
│ │ ├── features_test.py
│ │ ├── gfile.py
│ │ ├── image_differencer.py
│ │ ├── image_differencer_test.py
│ │ ├── memoize.py
│ │ ├── metrics.py
│ │ ├── named_array.py
│ │ ├── named_array_test.py
│ │ ├── np_util.py
│ │ ├── np_util_test.py
│ │ ├── point.py
│ │ ├── point_flag.py
│ │ ├── point_test.py
│ │ ├── portspicker.py
│ │ ├── portspicker_test.py
│ │ ├── proto_diff.py
│ │ ├── proto_diff_test.py
│ │ ├── protocol.py
│ │ ├── remote_controller.py
│ │ ├── renderer_ascii.py
│ │ ├── renderer_human.py
│ │ ├── replay/
│ │ │ ├── BUILD
│ │ │ ├── __init__.py
│ │ │ ├── replay_converter.py
│ │ │ ├── replay_observation_stream.py
│ │ │ ├── sc2_replay.py
│ │ │ ├── sc2_replay_test.py
│ │ │ ├── sc2_replay_utils.py
│ │ │ ├── sc2_replay_utils_test.py
│ │ │ └── test_data/
│ │ │ ├── replay_01.SC2Replay
│ │ │ ├── replay_01.skips.txt
│ │ │ ├── replay_02.SC2Replay
│ │ │ ├── replay_02.skips.txt
│ │ │ ├── replay_03.SC2Replay
│ │ │ ├── replay_03.skips.txt
│ │ │ ├── replay_04.SC2Replay
│ │ │ ├── replay_04.skips.txt
│ │ │ ├── replay_05.SC2Replay
│ │ │ ├── replay_05.skips.txt
│ │ │ ├── replay_06.SC2Replay
│ │ │ ├── replay_06.skips.txt
│ │ │ ├── replay_07.SC2Replay
│ │ │ ├── replay_07.skips.txt
│ │ │ ├── replay_08.SC2Replay
│ │ │ ├── replay_08.skips.txt
│ │ │ ├── replay_09.SC2Replay
│ │ │ └── replay_09.skips.txt
│ │ ├── replay.py
│ │ ├── resources.py
│ │ ├── run_parallel.py
│ │ ├── run_parallel_test.py
│ │ ├── sc_process.py
│ │ ├── static_data.py
│ │ ├── stopwatch.py
│ │ ├── stopwatch_test.py
│ │ ├── transform.py
│ │ ├── units.py
│ │ ├── upgrades.py
│ │ └── video_writer.py
│ ├── maps/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── ladder.py
│ │ ├── lib.py
│ │ ├── melee.py
│ │ ├── mini_games/
│ │ │ ├── BuildMarines.SC2Map
│ │ │ ├── CollectMineralShards.SC2Map
│ │ │ ├── CollectMineralsAndGas.SC2Map
│ │ │ ├── DefeatRoaches.SC2Map
│ │ │ ├── DefeatZerglingsAndBanelings.SC2Map
│ │ │ ├── FindAndDefeatZerglings.SC2Map
│ │ │ └── MoveToBeacon.SC2Map
│ │ └── mini_games.py
│ ├── run_configs/
│ │ ├── BUILD
│ │ ├── __init__.py
│ │ ├── lib.py
│ │ └── platforms.py
│ └── tests/
│ ├── BUILD
│ ├── __init__.py
│ ├── actions_test.py
│ ├── debug_test.py
│ ├── dummy_observation.py
│ ├── dummy_observation_test.py
│ ├── easy_scripted_test.py
│ ├── general_actions_test.py
│ ├── host_remote_agent_test.py
│ ├── multi_player_env_test.py
│ ├── multi_player_test.py
│ ├── obs_spec_test.py
│ ├── obs_test.py
│ ├── observer_test.py
│ ├── ping_test.py
│ ├── protocol_error_test.py
│ ├── random_agent_test.py
│ ├── render_test.py
│ ├── replay_obs_test.py
│ ├── step_mul_override_test.py
│ ├── utils.py
│ └── versions_test.py
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.pyc
*_pb2.py
================================================
FILE: CONTRIBUTING.md
================================================
Want to contribute? Great! First, read this page (including the small print at
the end).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement]
(https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
need to be sure of various other things—for instance that you'll tell us if you
know that your code infringes on other people's patents. You don't have to sign
the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
All submissions, including submissions by project members, require review. We
use Github pull requests for this purpose.
### The small print
Contributions made by corporations are covered by a different agreement than
the one above, the
[Software Grant and Corporate Contributor License Agreement]
(https://cla.developers.google.com/about/google-corporate).
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Google Inc.
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
http://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.
================================================
FILE: README.md
================================================
# PySC2 - StarCraft II Learning Environment
[PySC2](https://github.com/deepmind/pysc2) is [DeepMind](http://deepmind.com)'s
Python component of the StarCraft II Learning Environment (SC2LE). It exposes
[Blizzard Entertainment](http://blizzard.com)'s [StarCraft II Machine Learning
API](https://github.com/Blizzard/s2client-proto) as a Python RL Environment.
This is a collaboration between DeepMind and Blizzard to develop StarCraft II
into a rich environment for RL research. PySC2 provides an interface for RL
agents to interact with StarCraft 2, getting observations and sending actions.
We have published an accompanying
[blogpost](https://deepmind.com/blog/deepmind-and-blizzard-open-starcraft-ii-ai-research-environment/)
and [paper](https://arxiv.org/abs/1708.04782), which outlines our
motivation for using StarCraft II for DeepRL research, and some initial research
results using the environment.
## About
Disclaimer: This is not an official Google product.
If you use the StarCraft II Machine Learning API and/or PySC2 in your research,
please cite the [StarCraft II Paper](https://arxiv.org/abs/1708.04782)
You can reach us at [pysc2@deepmind.com](mailto:pysc2@deepmind.com).
# Quick Start Guide
## Get PySC2
### PyPI
The easiest way to get PySC2 is to use pip:
```shell
$ pip install pysc2
```
That will install the `pysc2` package along with all the required dependencies.
[virtualenv](https://pypi.python.org/pypi/virtualenv) can help manage your
dependencies. You may also need to upgrade pip: `pip install --upgrade pip`
for the `pysc2` install to work. If you're running on an older system you may
need to install `libsdl` libraries for the `pygame` dependency.
Pip will install a few of the binaries to your bin directory. `pysc2_play` can
be used as a shortcut to `python -m pysc2.bin.play`.
### From Source
Alternatively you can install latest PySC2 codebase from git master branch:
```shell
$ pip install --upgrade https://github.com/deepmind/pysc2/archive/master.zip
```
or from a local clone of the git repo:
```shell
$ git clone https://github.com/deepmind/pysc2.git
$ pip install --upgrade pysc2/
```
## Get StarCraft II
PySC2 depends on the full StarCraft II game and only works with versions that
include the API, which is 3.16.1 and above.
### Linux
Follow Blizzard's [documentation](https://github.com/Blizzard/s2client-proto#downloads) to
get the linux version. By default, PySC2 expects the game to live in
`~/StarCraftII/`. You can override this path by setting the `SC2PATH`
environment variable or creating your own run_config.
### Windows/MacOS
Install of the game as normal from [Battle.net](https://battle.net). Even the
[Starter Edition](http://battle.net/sc2/en/legacy-of-the-void/) will work.
If you used the default install location PySC2 should find the latest binary.
If you changed the install location, you might need to set the `SC2PATH`
environment variable with the correct location.
PySC2 should work on MacOS and Windows systems running Python 3.8+,
but has only been thoroughly tested on Linux. We welcome suggestions and patches
for better compatibility with other systems.
## Get the maps
PySC2 has many maps pre-configured, but they need to be downloaded into the SC2
`Maps` directory before they can be played.
Download the [ladder maps](https://github.com/Blizzard/s2client-proto#downloads)
and the [mini games](https://github.com/deepmind/pysc2/releases/download/v1.2/mini_games.zip)
and extract them to your `StarCraftII/Maps/` directory.
## Run an agent
You can run an agent to test the environment. The UI shows you the actions of
the agent and is helpful for debugging and visualization purposes.
```shell
$ python -m pysc2.bin.agent --map Simple64
```
It runs a random agent by default, but you can specify others if you'd like,
including your own.
```shell
$ python -m pysc2.bin.agent --map CollectMineralShards --agent pysc2.agents.scripted_agent.CollectMineralShards
```
You can also run two agents against each other.
```shell
$ python -m pysc2.bin.agent --map Simple64 --agent2 pysc2.agents.random_agent.RandomAgent
```
To specify the agent's race, the opponent's difficulty, and more, you can pass
additional flags. Run with `--help` to see what you can change.
## Play the game as a human
There is a human agent interface which is mainly used for debugging, but it can
also be used to play the game. The UI is fairly simple and incomplete, but it's
enough to understand the basics of the game. Also, it runs on Linux.
```shell
$ python -m pysc2.bin.play --map Simple64
```
In the UI, hit `?` for a list of the hotkeys. The most basic ones are: `F4` to
quit, `F5` to restart, `F8` to save a replay, and `Pgup`/`Pgdn` to control the
speed of the game. Otherwise use the mouse for selection and keyboard for
commands listed on the left.
The left side is a basic rendering. The right side is the feature layers that
the agent receives, with some coloring to make it more useful to us. You can
enable or disable RGB or feature layer rendering and their resolutions with
command-line flags.
## Watch a replay
Running an agent and playing as a human save a replay by default. You can watch
that replay by running:
```shell
$ python -m pysc2.bin.play --replay
```
This works for any replay as long as the map can be found by the game.
The same controls work as for playing the game, so `F4` to exit, `pgup`/`pgdn`
to control the speed, etc.
You can save a video of the replay with the `--video` flag.
## List the maps
[Maps](docs/maps.md) need to be configured before they're known to the
environment. You can see the list of known maps by running:
```shell
$ python -m pysc2.bin.map_list
```
## Run the tests
If you want to submit a pull request, please make sure the tests pass on both
python 2 and 3.
```shell
$ python -m pysc2.bin.run_tests
```
# Environment Details
For a full description of the specifics of how the environment is configured,
the observations and action spaces work read the
[environment documentation](docs/environment.md).
Note that an alternative to this environment is now available which provides
an enriched action and observation format using the C++ wrappers developed
for AlphaStar. See [the converter documentation](docs/converters.md) for more
information.
# Mini-game maps
The mini-game map files referenced in the paper are stored under `pysc2/maps/`
but must be installed in `$SC2PATH/Maps`. Make sure to follow the download
instructions above.
Maps are configured in the Python files in `pysc2/maps/`. The configs can set
player and time limits, whether to use the game outcome or curriculum score, and
a handful of other things. For more information about the maps, and how to
configure your own, read the [maps documentation](docs/maps.md).
# Replays
A replay lets you review what happened during a game. You can see the actions
and observations that each player made as they played.
Blizzard is releasing a large number of anonymized 1v1 replays played on the
ladder. You can find instructions for how to get the
[replay files](https://github.com/Blizzard/s2client-proto#downloads) on their
site. You can also review your own replays.
Replays can be played back to get the observations and actions made during that
game. The observations are rendered at the resolution you request, so may differ
from what the human actually saw. Similarly the actions specify a point, which
could reflect a different pixel on the human's screen, so may not have an exact
match in our observations, though they should be fairly similar.
Replays are version dependent, so a 3.16 replay will fail in a 3.16.1 or 3.17
binary.
You can visualize the replays with the full game, or with `pysc2.bin.play`.
Alternatively you can run `pysc2.bin.replay_actions` to process many replays
in parallel.
================================================
FILE: WORKSPACE
================================================
workspace(name = "pysc2")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("//bazel:create_external_repos.bzl", "pysc2_create_external_repos")
pysc2_create_external_repos(pysc2_repo_name = "pysc2")
load("//bazel:setup_external_repos.bzl", "pysc2_setup_external_repos")
pysc2_setup_external_repos()
bind(
name = "python_headers",
actual = "@local_config_python//:python_headers",
)
http_archive(
name = "bazel_skylib",
strip_prefix = "bazel-skylib-main",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/main.zip"],
)
http_archive(
name = "rules_python",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.4.0/rules_python-0.4.0.tar.gz",
sha256 = "954aa89b491be4a083304a2cb838019c8b8c3720a7abb9c4cb81ac7a24230cea",
)
load("@rules_python//python:pip.bzl", "pip_install")
http_archive(
name = "rules_cc",
urls = ["https://github.com/bazelbuild/rules_cc/archive/68cb652a71e7e7e2858c50593e5a9e3b94e5b9a9.zip"],
strip_prefix = "rules_cc-68cb652a71e7e7e2858c50593e5a9e3b94e5b9a9",
sha256 = "1e19e9a3bc3d4ee91d7fcad00653485ee6c798efbbf9588d40b34cbfbded143d",
)
load("@rules_cc//cc:repositories.bzl", "rules_cc_dependencies")
rules_cc_dependencies()
# Create a central external repo, @my_deps, that contains Bazel targets for all the
# third-party packages specified in the requirements.txt file.
pip_install(
name = "my_deps",
requirements = "@//bazel:requirements.txt",
)
http_archive(
name = "rules_proto",
sha256 = "66bfdf8782796239d3875d37e7de19b1d94301e8972b3cbd2446b332429b4df1",
strip_prefix = "rules_proto-4.0.0",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0.tar.gz",
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0.tar.gz",
],
)
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
# C++ dependencies.
http_archive(
name = "com_google_googletest",
sha256 = "ff7a82736e158c077e76188232eac77913a15dac0b22508c390ab3f88e6d6d86",
strip_prefix = "googletest-b6cd405286ed8635ece71c72f118e659f4ade3fb",
urls = [
"https://storage.googleapis.com/mirror.tensorflow.org/github.com/google/googletest/archive/b6cd405286ed8635ece71c72f118e659f4ade3fb.zip",
"https://github.com/google/googletest/archive/b6cd405286ed8635ece71c72f118e659f4ade3fb.zip",
],
)
http_archive(
name = "com_google_absl",
sha256 = "35f22ef5cb286f09954b7cc4c85b5a3f6221c9d4df6b8c4a1e9d399555b366ee", # SHARED_ABSL_SHA
strip_prefix = "abseil-cpp-997aaf3a28308eba1b9156aa35ab7bca9688e9f6",
urls = [
"https://storage.googleapis.com/mirror.tensorflow.org/github.com/abseil/abseil-cpp/archive/997aaf3a28308eba1b9156aa35ab7bca9688e9f6.tar.gz",
"https://github.com/abseil/abseil-cpp/archive/997aaf3a28308eba1b9156aa35ab7bca9688e9f6.tar.gz",
],
)
================================================
FILE: bazel/BUILD
================================================
licenses(["notice"])
exports_files(
[
"BUILD.dm_env",
"BUILD.dm_env_rpc",
"BUILD.s2protocol",
"protobuf.patch",
"requirements.txt",
"s2clientprotocol.patch",
],
visibility = ["//visibility:public"],
)
================================================
FILE: bazel/BUILD.dm_env
================================================
load("@my_deps//:requirements.bzl", "requirement")
licenses(["notice"])
py_library(
name = "dm_env",
srcs = [
"dm_env/__init__.py",
"dm_env/_environment.py",
"dm_env/_metadata.py",
],
visibility = ["//visibility:public"],
deps = [
"@dm_env_archive//:specs",
],
)
py_library(
name = "specs",
srcs = [
"dm_env/specs.py",
],
visibility = ["//visibility:public"],
)
py_library(
name = "test_utils",
srcs = [
"dm_env/_abstract_test_mixin.py",
"dm_env/test_utils.py",
],
deps = [
requirement("dm-tree"),
],
visibility = ["//visibility:public"],
)
================================================
FILE: bazel/BUILD.dm_env_rpc
================================================
load("@com_github_grpc_grpc//bazel:python_rules.bzl", "py_proto_library")
licenses(["notice"])
proto_library(
name = "dm_env_rpc_proto",
srcs = ["dm_env_rpc/v1/dm_env_rpc.proto"],
visibility = ["//visibility:public"],
deps = [
"@com_google_googleapis//google/rpc:status_proto",
"@com_google_protobuf//:any_proto",
],
)
cc_proto_library(
name = "dm_env_rpc_cc_proto",
visibility = ["//visibility:public"],
deps = [":dm_env_rpc_proto"],
)
py_proto_library(
name = "dm_env_rpc_pb2",
deps = ["@dm_env_rpc_archive//:dm_env_rpc_proto"],
)
py_proto_library(
name = "status_pb2",
deps = ["@com_google_googleapis//google/rpc:status_proto"],
)
py_library(
name = "dm_env_rpc",
srcs = glob(["dm_env_rpc/**/*.py"]),
visibility = ["//visibility:public"],
deps = [
":dm_env_rpc_pb2",
":status_pb2",
],
)
================================================
FILE: bazel/BUILD.s2protocol
================================================
licenses(["notice"])
py_library(
name = "s2protocol",
srcs = [
"s2protocol/__init__.py",
"s2protocol/attributes.py",
"s2protocol/build.py",
"s2protocol/compat.py",
"s2protocol/decoders.py",
"s2protocol/encoders.py",
"s2protocol/namespaces.py",
],
visibility = ["//visibility:public"],
)
py_library(
name = "versions",
srcs = glob(["s2protocol/versions/*.py"]) + ["s2protocol/__init__.py"],
visibility = ["//visibility:public"],
deps = [":s2protocol"],
)
================================================
FILE: bazel/create_external_repos.bzl
================================================
# Copyright 2021 DeepMind Technologies Ltd. All rights reserved.
#
# 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
#
# http://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.
"""Creates external repos needed by PySC2 and by its consumers."""
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
def pysc2_create_external_repos(pysc2_repo_name):
"""Creates external repos needed by PySC2 and by its consumers.
Args:
pysc2_repo_name: The name of the PySC2 repo, as instantiated in
the local WORKSPACE. When executing this function from the pysc2
WORKSPACE, this name is "pysc2". But external consumers shouldn't
use that name as it leads to Python import problems due to the
pysc2/pysc2 subdirectory. Hence a name such as "pysc2_archive"
is recommended.
Example:
http_archive(
name = "pysc2_archive",
# ...
)
load("@pysc2_archive//bazel:create_external_repos.bzl",
"pysc2_create_external_repos")
pysc2_create_external_repos(pysc2_repo_name = "pysc2_archive")
"""
if not native.existing_rule("absl_py"):
# It is important that this isn't named simply 'absl' as the project has a
# subdirectory named that, and Python module lookup fails if we have absl/absl.
http_archive(
name = "absl_py",
strip_prefix = "abseil-py-main",
urls = ["https://github.com/abseil/abseil-py/archive/main.zip"],
)
# Required by glog.
if not native.existing_rule("com_github_gflags_gflags"):
http_archive(
name = "com_github_gflags_gflags",
sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf",
strip_prefix = "gflags-2.2.2",
urls = [
"https://github.com/gflags/gflags/archive/v2.2.2.tar.gz",
],
)
if not native.existing_rule("glog"):
http_archive(
name = "glog",
sha256 = "6281aa4eeecb9e932d7091f99872e7b26fa6aacece49c15ce5b14af2b7ec050f",
urls = [
"https://github.com/google/glog/archive/96a2f23dca4cc7180821ca5f32e526314395d26a.zip",
],
strip_prefix = "glog-96a2f23dca4cc7180821ca5f32e526314395d26a",
)
if not native.existing_rule("com_google_protobuf"):
http_archive(
name = "com_google_protobuf",
patches = ["@" + pysc2_repo_name + "//bazel:protobuf.patch"],
urls = ["https://github.com/protocolbuffers/protobuf/archive/refs/tags/v3.19.1.zip"],
strip_prefix = "protobuf-3.19.1",
)
if not native.existing_rule("com_google_googleapis"):
http_archive(
name = "com_google_googleapis",
sha256 = "1f742f6cafe616fe73302db010e0b7ee6579cb1ce06010427b7d0995cbd80ce4",
strip_prefix = "googleapis-6a813acf535e4746fa4a135ce23547bb6425c26d",
urls = [
"https://github.com/googleapis/googleapis/archive/6a813acf535e4746fa4a135ce23547bb6425c26d.tar.gz",
],
)
if not native.existing_rule("pybind11_bazel"):
http_archive(
name = "pybind11_bazel",
strip_prefix = "pybind11_bazel-master",
urls = ["https://github.com/pybind/pybind11_bazel/archive/refs/heads/master.zip"],
)
if not native.existing_rule("pybind11"):
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11.BUILD",
strip_prefix = "pybind11-2.8.1",
urls = ["https://github.com/pybind/pybind11/archive/v2.8.1.tar.gz"],
)
if not native.existing_rule("s2client_proto"):
http_archive(
name = "s2client_proto",
urls = ["https://github.com/Blizzard/s2client-proto/archive/refs/heads/master.zip"],
strip_prefix = "s2client-proto-master",
patches = ["@" + pysc2_repo_name + "//bazel:s2clientprotocol.patch"],
)
if not native.existing_rule("s2protocol_archive"):
http_archive(
name = "s2protocol_archive",
urls = ["https://github.com/Blizzard/s2protocol/archive/refs/heads/master.zip"],
strip_prefix = "s2protocol-master",
build_file = "@" + pysc2_repo_name + "//bazel:BUILD.s2protocol",
)
if not native.existing_rule("dm_env_archive"):
# We can't use the wheels for dm_env because that pulls in
# proto code which leads to incompatibilities with our our protos.
http_archive(
name = "dm_env_archive",
urls = ["https://github.com/deepmind/dm_env/archive/refs/heads/master.zip"],
strip_prefix = "dm_env-master",
build_file = "@" + pysc2_repo_name + "//bazel:BUILD.dm_env",
)
if not native.existing_rule("dm_env_rpc_archive"):
# We can't use the wheels for dm_env_rpc because that pulls in
# proto code which leads to incompatibilities with our our protos.
http_archive(
name = "dm_env_rpc_archive",
urls = ["https://github.com/deepmind/dm_env_rpc/archive/refs/heads/master.zip"],
strip_prefix = "dm_env_rpc-master",
build_file = "@" + pysc2_repo_name + "//bazel:BUILD.dm_env_rpc",
)
if not native.existing_rule("com_github_grpc_grpc"):
http_archive(
name = "com_github_grpc_grpc",
strip_prefix = "grpc-master",
urls = ["https://github.com/grpc/grpc/archive/refs/heads/master.zip"],
)
================================================
FILE: bazel/protobuf.patch
================================================
--- BUILD
+++ BUILD
@@ -889,6 +889,8 @@
"//conditions:default": [],
":use_fast_cpp_protos": ["//external:python_headers"],
}),
+
+ visibility = ["//visibility:public"],
)
config_setting(
================================================
FILE: bazel/requirements.txt
================================================
deepdiff
dm-tree
mock
mpyq
numpy>=1.10.0
portpicker>=1.2.0
psutil
pygame
requests
s2protocol
sk-video
typing-extensions
websocket-client
================================================
FILE: bazel/s2clientprotocol.patch
================================================
+++ s2clientprotocol/BUILD 2021-11-18 12:14:07.885666323 +0000
@@ -0,0 +1,255 @@
+load("@com_github_grpc_grpc//bazel:python_rules.bzl", "py_proto_library")
+
+package(default_visibility = ["//visibility:public"])
+
+proto_library(
+ name = "common_proto",
+ srcs = ["common.proto"],
+)
+
+cc_proto_library(
+ name = "common_cc_proto",
+ deps = [":common_proto"],
+)
+
+py_proto_library(
+ name = "common_pb2",
+ deps = [":common_proto"],
+)
+
+py_library(
+ name = "common_py_pb2",
+ deps = [":common_pb2"],
+)
+
+proto_library(
+ name = "data_proto",
+ srcs = ["data.proto"],
+ deps = [":common_proto"],
+)
+
+cc_proto_library(
+ name = "data_cc_proto",
+ deps = [":data_proto"],
+)
+
+py_proto_library(
+ name = "data_pb2",
+ deps = [":data_proto"],
+)
+
+py_library(
+ name = "data_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":data_pb2",
+ ],
+)
+
+proto_library(
+ name = "debug_proto",
+ srcs = ["debug.proto"],
+ deps = [":common_proto"],
+)
+
+cc_proto_library(
+ name = "debug_cc_proto",
+ deps = [":debug_proto"],
+)
+
+py_proto_library(
+ name = "debug_pb2",
+ deps = [":debug_proto"],
+)
+
+py_library(
+ name = "debug_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":debug_pb2",
+ ],
+)
+
+proto_library(
+ name = "error_proto",
+ srcs = ["error.proto"],
+)
+
+cc_proto_library(
+ name = "error_cc_proto",
+ deps = [":error_proto"],
+)
+
+py_proto_library(
+ name = "error_pb2",
+ deps = [":error_proto"],
+)
+
+py_library(
+ name = "error_py_pb2",
+ deps = [
+ ":error_pb2",
+ ],
+)
+
+proto_library(
+ name = "query_proto",
+ srcs = ["query.proto"],
+ deps = [
+ ":common_proto",
+ ":error_proto",
+ ],
+)
+
+cc_proto_library(
+ name = "query_cc_proto",
+ deps = [":query_proto"],
+)
+
+py_proto_library(
+ name = "query_pb2",
+ deps = [":query_proto"],
+)
+
+py_library(
+ name = "query_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":error_pb2",
+ ":query_pb2",
+ ],
+)
+
+proto_library(
+ name = "raw_proto",
+ srcs = ["raw.proto"],
+ deps = [":common_proto"],
+)
+
+cc_proto_library(
+ name = "raw_cc_proto",
+ deps = [":raw_proto"],
+)
+
+py_proto_library(
+ name = "raw_pb2",
+ deps = [":raw_proto"],
+)
+
+py_library(
+ name = "raw_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":raw_pb2",
+ ],
+)
+
+proto_library(
+ name = "sc2api_proto",
+ srcs = ["sc2api.proto"],
+ deps = [
+ ":common_proto",
+ ":data_proto",
+ ":debug_proto",
+ ":error_proto",
+ ":query_proto",
+ ":raw_proto",
+ ":score_proto",
+ ":spatial_proto",
+ ":ui_proto",
+ ],
+)
+
+cc_proto_library(
+ name = "sc2api_cc_proto",
+ deps = [":sc2api_proto"],
+)
+
+py_proto_library(
+ name = "sc2api_pb2",
+ deps = [":sc2api_proto"],
+)
+
+py_library(
+ name = "sc2api_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":data_pb2",
+ ":debug_pb2",
+ ":error_pb2",
+ ":query_pb2",
+ ":raw_pb2",
+ ":sc2api_pb2",
+ ":score_pb2",
+ ":spatial_pb2",
+ ":ui_pb2",
+ ],
+)
+
+proto_library(
+ name = "score_proto",
+ srcs = ["score.proto"],
+)
+
+cc_proto_library(
+ name = "score_cc_proto",
+ deps = [":score_proto"],
+)
+
+py_proto_library(
+ name = "score_pb2",
+ deps = [":score_proto"],
+)
+
+py_library(
+ name = "score_py_pb2",
+ deps = [
+ ":score_pb2",
+ ],
+)
+
+proto_library(
+ name = "spatial_proto",
+ srcs = ["spatial.proto"],
+ deps = [":common_proto"],
+)
+
+cc_proto_library(
+ name = "spatial_cc_proto",
+ deps = [":spatial_proto"],
+)
+
+py_proto_library(
+ name = "spatial_pb2",
+ deps = [":spatial_proto"],
+)
+
+py_library(
+ name = "spatial_py_pb2",
+ deps = [
+ ":common_pb2",
+ ":spatial_pb2",
+ ],
+)
+
+proto_library(
+ name = "ui_proto",
+ srcs = ["ui.proto"],
+)
+
+cc_proto_library(
+ name = "ui_cc_proto",
+ deps = [":ui_proto"],
+)
+
+py_proto_library(
+ name = "ui_pb2",
+ deps = [":ui_proto"],
+)
+
+py_library(
+ name = "ui_py_pb2",
+ deps = [
+ ":ui_pb2",
+ ],
+)
+++ WORKSPACE 2021-11-18 12:14:07.885666323 +0000
@@ -0,0 +1,21 @@
+workspace(name = "s2client-proto")
+
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
+# The protobuf library, for protoc.
+git_repository(
+ name = "com_google_protobuf",
+ remote = "https://github.com/protocolbuffers/protobuf",
+ branch = "master",
+)
+load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
+protobuf_deps()
+
+# GRPC, for the py_proto_library rule.
+git_repository(
+ name = "com_github_grpc_grpc",
+ remote = "https://github.com/grpc/grpc.git",
+ branch = "master",
+)
+load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
+grpc_deps()
================================================
FILE: bazel/setup_external_repos.bzl
================================================
# Copyright 2021 DeepMind Technologies Ltd. All rights reserved.
#
# 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
#
# http://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.
"""Sets up external repos needed by PySC2 and by its consumers."""
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
load("@pybind11_bazel//:python_configure.bzl", "python_configure")
def pysc2_setup_external_repos():
"""Sets up external repos needed by PySC2 and by its consumers.
Ideally these would happen as part of pysc2_deps, but load statements are
only permitted at the top level currently, necessitating this workaround.
"""
protobuf_deps()
switched_rules_by_language(
name = "com_google_googleapis_imports",
cc = True,
python = True,
)
python_configure(name = "local_config_python")
grpc_deps()
================================================
FILE: docs/bazel.md
================================================
# Building with Bazel
Prior to the inclusion of C++ code it was sufficient to install the
[pip](https://pypi.org/project/pip/) dependencies of PySC2 and to execute the
Python code directly using the Python interpreter. This is still possible if
installing from a [wheel](https://pypi.org/project/wheel/) with a pre-built
distribution matching the platform on which you are running, else when not
referencing any of the C++ code. Where that is not the case, or where you would
like to make changes to the C++ code, a build system becomes necessary. We
support the use of [Bazel](https://bazel.build/), Google's open-source build
tool, for this purpose.
## Supported platforms
We support building with Bazel on Ubuntu Linux with C++ 17. In future we may
support other platforms, should there be demand.
## Example
First of all,
[get Bazel](https://docs.bazel.build/versions/main/install-ubuntu.html). Next,
you may need the Python development environment.
```shell
$ sudo apt update
$ sudo apt install python3 python3-dev python3-venv
```
Build all PySC2 targets (from the workspace root).
```shell
$ bazel build --cxxopt='-std=c++17' ...
```
Run some tests.
```shell
$ bazel test --cxxopt='-std=c++17' pysc2/lib/...
```
Beyond that, everything should be the same as running Python directly as
described in the readme, only rather than using the Python interpreter you use
Bazel to run. For instance...
```shell
$ python -m pysc2.bin.agent --map Simple64
```
becomes...
```shell
$ bazel run --cxxopt='-std=c++17' pysc2/bin:agent -- --map Simple64
```
You may wish to use a [.bazelrc file](https://docs.bazel.build/versions/main/guide.html#bazelrc-the-bazel-configuration-file) to avoid the need to repeatedly specify command-line options, for instance `--cxxopt='-std=c++17'`.
================================================
FILE: docs/converters.md
================================================
# Environment converters
## Overview
The SC2 API uses protos to communicate actions to and observations from the
environment. `sc2_env.py` contains code to marshall between these protos and a
more neural network friendly dictionary of numpy arrays, with accompanying
environment specs. During development of AlphaStar this code was translated to
C++, and new features added. This code is now available in PySC2. See
`converter.cc`, `converter.py`, `converted_env.py`, `replay_converter.py`, for
instance.
Converters are instantiated per-agent, per-episode. They are configurable via
the `ConverterSettings` proto; see `converter.proto`. Though they can be used
directly, it is more typical to use them as part of an environment wrapper
(`converted_env.py`), or via the replay conversion code (`replay_converter.py`).
Note that converters don't have to be used from Python. The C++ code is wrapped
using [pybind11](https://github.com/pybind/pybind11), but may also be used
directly.
Note also that, because converters are implemented in C++, changes to them
require compilation. See [this page](bazel.md) for further information.
## Supported platforms
Use of converters requires either [building with Bazel](bazel.md) or use of an
appropriate pre-built wheel. Only Linux is supported at present.
## Raw mode
If raw settings are provided as part of the converter settings the converter
will operate in raw mode. This mode is so called because it provides raw access
to numerical data, with only limited spatial data for the minimap. Actions are
specified directly for units using their tags. There is support for limiting the
data which the agent receives to the view implied by the current camera
position, similar to how humans perceive the game.
### Action spec
name | shape | dtype | min | max | enabled?
--------------- | -------------------------------- | ----- | --- | ------------------------------------------- | --------
function | () | int32 | 0 | `num_action_types` | always
delay | () | int32 | 1 | 127 | always
queued | () | int32 | 0 | 1 | always
repeat | () | int32 | 0 | 2 | `raw.enable_action_repeat`
target_unit_tag | () | int32 | 0 | `raw.max_unit_count` - 1 | always
unit_tags | (`raw.max_unit_selection_size`,) | int32 | 0 | `raw.max_unit_count` | always
world | () | int32 | 0 | `raw.resolution.x` * `raw.resolution.y` - 1 | always
### Observation spec
name | shape | dtype | min | max | enabled?
------------------------------ | ------------------------------------------- | ----- | --------- | ------------------------------------------- | --------
action/function | () | int32 | 0 | `num_action_types` | `supervised`
action/delay | () | int32 | 1 | 127 | `supervised`
action/queued | () | int32 | 0 | 1 | `supervised`
action/repeat | () | int32 | 0 | 2 | `supervised` and `raw.enable_action_repeat`
action/target_unit_tag | () | int32 | 1 | `raw.max_unit_count` - 1 | `supervised`
action/unit_tags | (`raw.max_unit_selection_size`,) | int32 | 0 | `raw.max_unit_count` | `supervised`
action/world | () | int32 | 0 | `raw.resolution.x` * `raw.resolution.y` - 1 | `supervised`
away_race_observed | () | int32 | - | - | always
away_race_requested | () | int32 | - | - | always
camera | (`raw.resolution.x`, `raw.resolution.y`) | int32 | 0 | 1 | `raw.camera`
camera_position | (2,) | int32 | - | - | `raw.use_camera_position`
camera_size | (2,) | int32 | - | - | `raw.use_camera_position`
game_loop | () | int32 | - | - | always
home_race_requested | () | int32 | - | - | always
minimap_alerts | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'alerts' in `minimap features`
minimap_buildable | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'buildable' in `minimap features`
minimap_creep | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'creep' in `minimap features`
minimap_height_map | (`minimap.x`, `minimap.y`) | uint8 | 0 | 255 | 'height_map' in `minimap features`
minimap_pathable | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'pathable' in `minimap features`
minimap_player_relative | (`minimap.x`, `minimap.y`) | uint8 | 0 | 4 | 'player_relative' in `minimap features`
minimap_visibility_map | (`minimap.x`, `minimap.y`) | uint8 | 0 | 3 | 'visibility_map' in `minimap features`
mmr | () | int32 | - | - | always
opponent_player | (10,) | int32 | - | - | `add_opponent_features`
opponent_unit_counts_bow | (`num_unit_types`,) | int32 | - | - | `add_opponent_features`
opponent_upgrades_fixed_length | (`max_num_upgrades`,) | int32 | 0 | `num_upgrade_types + 1` | `add_opponent_features`
player | (11,) | int32 | - | - | always
raw_units | (`max_unit_count`, `num_unit_features` + 2) | int32 | 0 | varies | always
unit_counts_bow | (`num_unit_types`,) | int32 | - | - | always
upgrades_fixed_length | (`max_num_upgrades`,) | int32 | minimum=0 | maximum=`num_upgrade_types` + 1 | always
## Visual mode
If visual settings are provided as part of the converter settings the converter
will operate in visual mode. In this mode the majority of the data provided to
the agent is represented spatially, with planes reflecting the attributes of
units on the screen and minimap. Actions in this mode are more similar to how a
human would interact, point-and-click.
### Action spec
| name | shape | dtype | min | max | enabled? |
| ----------------- | ----- | ----- | --- | ------------------- | -------- |
| function | () | int32 | 0 | `num_action_types` | always |
| build_queue_id | () | int32 | 0 | 9 | always |
| control_group_act | () | int32 | 0 | 4 | always |
| control_group_id | () | int32 | 0 | 9 | always |
| delay | () | int32 | 1 | 127 | always |
| minimap | () | int32 | 0 | `minimap.x` * | always |
: : : : : `minimap.y` - 1 : :
| queued | () | int32 | 0 | 1 | always |
| screen | () | int32 | 0 | `visual.screen.x` * | always |
: : : : : `visual.screen.y` - : :
: : : : : 1 : :
| screen2 | () | int32 | 0 | `visual.screen.x` * | always |
: : : : : `visual.screen.y` - : :
: : : : : 1 : :
| select_add | () | int32 | 0 | 1 | always |
| select_point_act | () | int32 | 0 | 3 | always |
| select_unit_act | () | int32 | 0 | 3 | always |
| select_unit_id | () | int32 | 0 | 499 | always |
| select_worker | () | int32 | 0 | 3 | always |
| unload_id | () | int32 | 0 | 499 | always |
### Observation spec
name | shape | dtype | min | max | enabled?
------------------------------ | -------------------------------------- | ----- | --- | ----------------------------------------- | --------
action/build_queue_id | () | int32 | 0 | 9 | `supervised`
action/control_group_act | () | int32 | 0 | 4 | `supervised`
action/control_group_id | () | int32 | 0 | 9 | `supervised`
action/delay | () | int32 | 1 | 127 | `supervised`
action/function | () | int32 | 0 | `num_action_types` | `supervised`
action/minimap | () | int32 | 0 | `minimap.x` * `minimap.y` - 1 | `supervised`
action/queued | () | int32 | 0 | 1 | `supervised`
action/screen | () | int32 | 0 | `visual.screen.x` * `visual.screen.y` - 1 | `supervised`
action/screen2 | () | int32 | 0 | `visual.screen.x` * `visual.screen.y` - 1 | `supervised`
action/select_add | () | int32 | 0 | 1 | `supervised`
action/select_point_act | () | int32 | 0 | 3 | `supervised`
action/select_unit_act | () | int32 | 0 | 3 | `supervised`
action/select_unit_id | () | int32 | 0 | 499 | `supervised`
action/select_worker | () | int32 | 0 | 3 | `supervised`
action/unload_id | () | int32 | 0 | 499 | `supervised`
available_actions | (`num_action_types`,) | int32 | - | - | always
away_race_observed | () | int32 | - | - | always
away_race_requested | () | int32 | - | - | always
game_loop | () | int32 | | - | -
home_race_requested | () | int32 | - | - | always
minimap_alerts | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'alerts' in `minimap_features`
minimap_buildable | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'buildable' in `minimap_features`
minimap_camera | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'camera' in `minimap_features`
minimap_creep | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'creep' in `minimap_features`
minimap_height_map | (`minimap.x`, `minimap.y`) | uint8 | 0 | 255 | 'height_map' in `minimap_features`
minimap_pathable | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'pathable' in `minimap_features`
minimap_player_relative | (`minimap.x`, `minimap.y`) | uint8 | 0 | 4 | 'player_relative' in `minimap_features`
minimap_selected | (`minimap.x`, `minimap.y`) | uint8 | 0 | 1 | 'selected' in `minimap_features`
minimap_visibility_map | (`minimap.x`, `minimap.y`) | uint8 | 0 | 3 | 'visibility_amp' in `minimap_features`
mmr | () | int32 | - | - | always
opponent_player | (10,) | int32 | - | - | `add_opponent_features`
opponent_unit_counts_bow | (217,) | int32 | - | - | `add_opponent_features`
opponent_upgrades_fixed_length | (`max_num_upgrades`,) | int32 | 0 | `num_upgrade_types` - 1 | `add_opponent_features`
player | (11,) | int32 | - | - | always
screen_active | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'active' in `visual.screen_features`
screen_blip | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'blip' in `visual.screen_features`
screen_buff_duration | (`visual.screen.x`, `visual.screen.y` | uint8 | 0 | 255 | 'buff_duration' in `visual.screen_features`
screen_buffs | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 47 | 'buffs' in `visual.screen_features`
screen_build_progress | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'build_progress' in `visual.screen_features`
screen_buildable | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'buildable' in `visual.screen_features`
screen_cloaked | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'cloaked' in `visual.screen_features`
screen_creep | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'creep' in `visual.screen_features`
screen_effects | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 15 | 'effects' in `visual.screen_features`
screen_hallucinations | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'hallucinations' in `visual.screen_features`
screen_height_map | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'height_map' in `visual.screen_features`
screen_pathable | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'pathable' in `visual.screen_features`
screen_player_relative | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 4 | 'player_relative' in `visual.screen_features`
screen_power | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'power' in `visual.screen_features`
screen_selected | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 1 | 'selected' in `visual.screen_features`
screen_unit_density_aa | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'unit_density_aa' in `visual.screen_features`
screen_unit_energy_ratio | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'unit_energy_ratio' in `visual.screen_features`
screen_unit_hit_points_ratio | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'unit_hit_points_ratio' in `visual.screen_features`
screen_unit_shields_ratio | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 255 | 'unit_shields_ratio' in `visual.screen_features`
screen_unit_type | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | `num_unit_types` | 'unit_type' in `visual.screen_features`
screen_visibility_map | (`visual.screen.x`, `visual.screen.y`) | uint8 | 0 | 3 | 'visibility_map' in `visual.screen_features`
unit_counts_bow | (`num_unit_types`,) | int32 | - | - | always
upgrades_fixed_length | (`max_num_upgrades`,) | int32 | 0 | `num_upgrade_types`-1 | always
================================================
FILE: docs/environment.md
================================================
## Environment Table of Contents
- [Starcraft II](#starcraft-ii)
- [What is StarCraft II](#what-is-starcraft-ii)
- [Versions](#versions)
- [Game and Action Speed](#game-and-action-speed)
- [Game speed](#game-speed)
- [APM Calculation](#apm-calculation)
- [APM and fairness](#apm-and-fairness)
- [Determinism and Randomness](#determinism-and-randomness)
- [Actions and Observations](#actions-and-observations)
- [Observation](#observation)
- [Spatial/Visual](#spatialvisual)
- [RGB Pixels](#rgb-pixels)
- [Feature layers](#feature-layers)
- [Minimap](#minimap)
- [Screen](#screen)
- [Structured](#structured)
- [General player information](#general-player-information)
- [Control groups](#control-groups)
- [Single Select](#single-select)
- [Multi Select](#multi-select)
- [Cargo](#cargo)
- [BuildQueue](#build-queue)
- [AvailableActions](#available-actions)
- [LastActions](#last-actions)
- [ActionsResult](#action-result)
- [Alerts](#alerts)
- [Actions](#actions)
- [List of actions](#list-of-actions)
- [Action categories](#action-categories)
- [General vs Specific actions](#general-vs-specific-actions)
- [Example usage](#example-usage)
- [RL Environment](#rl-environment)
- [Environment wrappers](#environment-wrappers)
- [Agents](#agents)
## StarCraft II
### What is StarCraft II
[StarCraft II](https://en.wikipedia.org/wiki/StarCraft_II:_Legacy_of_the_Void)
is a [Real Time Strategy
(RTS)](https://en.wikipedia.org/wiki/Real-time_strategy) game written by
[Blizzard](http://blizzard.com/). It's the successor to [StarCraft
Broodwar](https://en.wikipedia.org/wiki/StarCraft:_Brood_War), which is one of
the most successful RTS games. StarCraft II is played by millions of people, and
has a [professional league](https://wcs.starcraft2.com/en-us/).
The [goal in StarCraft](http://us.battle.net/sc2/en/game/guide/whats-sc2) is to
build a base, manage an economy, build an army, and destroy your enemies. You
control your base and army from a third person perspective, then multi-task and
micro-manage your units for maximum effect. StarCraft has 3 distinct races:
[Terran](https://www.youtube.com/watch?v=Fmu8PsUDDtQ),
[Protoss](https://www.youtube.com/watch?v=m0g0MpllFCs) and
[Zerg](https://www.youtube.com/watch?v=Lq74R7wWAnQ), which have different units
and strategies.
The game has a single player campaign, though most people play multiplayer on
[Battle.net](http://battle.net/sc2/en/). There are built-in bots, but they are
fairly weak and predictable, and the stronger ones cheat.
There are many resources online for learning about Starcraft, including
[Battle.net](http://battle.net/sc2/en/),
[Liquipedia](http://liquipedia.net/starcraft2/StarCraft) and
[Wikia](http://starcraft.wikia.com/). For map making check out
[SC2Mapster](https://sc2mapster.gamepedia.com/SC2Mapster_Wiki).
### Versions
Blizzard regularly updates StarCraft II. These updates are roughly monthly, can
introduce new features, and often have minor gameplay and balance changes to
make the races more even. Replays are tied to the specific version they were
generated with.
### Game and Action Speed
Being a real-time strategy game means the game runs in real-time. In reality
though, the simulation updates 16-22 times per second, depending on your [game
speed](http://wiki.teamliquid.net/starcraft2/Game_Speed), and all the
intermediate rendered frames are just interpolated.
#### Game speed
Acting through an API allows you to control the clock. Instead of running at
16-22 steps per second, you can step as fast or as slow as you want, when you
want. This allows you to run much faster than real-time if the agent is capable,
or much slower if the agent needs to pause to spend some time learning. This
also means there is never any lag. The max speed depends on the step_mul/render
frequency, and the scene complexity/unit count. 10x real-time is not uncommon
and can be much higher for simple maps.
#### APM Calculation
The game calculates and reports APM in a non-obvious way. It isn't obvious how
to count the action of moving the camera with edge scrolling, or how many
actions is building a building. Here are the rules of how it's actually counted
by the game.
There are actually two types reported by the game:
* Actions Per Minute (APM): counts every action.
* Effective Actions Per Minute (EPM): filters out actions that have no effect
(eg: redundant selections).
Different actions count as different number of actions:
* Commands with target = 2 (eg: move, attack, build building)
* Commands with no target = 1 (eg: stop, train unit, unload cargo)
* Smart = 1 (right click)
* Selection and control groups = 1
* Everything else = 0 (eg: camera movement)
The in game replay UI exposes this with two different time intervals: average
(average over the entire game so far), and current (average over the last 5
seconds). The API only exposes the average APM.
#### APM and fairness
Humans can't do one action per frame. They range from 30-500 actions per minute
(APM), with 30 being a beginner, 60-100 being average, and professionals
being >200 (over 3 per second!). This is trivial compared to what a fast bot is
capable of though. With the [BWAPI](http://bwapi.github.io/) they control units
individually and routinely go over 5000 APM accomplishing things that are
clearly impossible for humans and considered
[unfair](https://youtu.be/IKVFZ28ybQs?t=45s) or even
[broken](https://youtu.be/0EYH-csTttw?t=28s). Even without controlling the units
individually it would be unfair to be able to act much faster with high
precision.
To at least resemble playing fairly it is a good idea to artificially limit the
APM. The easy way is to limit how often the agent gets observations and can make
an action, and limit it to one action per observation. For example you can do
this by only taking every `N`th observation, where `N` is up for debate. A value
of 20 is roughly equal to 50 apm while 5 is roughly 200 apm, so that's a
reasonable range to play with. A more sophisticated way is to give the agent
every observation but limit the number of actions that actually have an effect,
forcing it to mainly make no-ops which wouldn't count as actions.
It's probably better to consider all actions as equivalent, including camera
movement, since allowing very fast camera movement could allow agents to cheat.
### Determinism and Randomness
Starcraft II is mostly deterministic, but it does have some randomness mainly
for cosmetic reasons. The two main random elements are weapon speed and update
order.
Weapon randomness is essentially how fast a unit can get its next shot off after
firing and is between -1 to +2 game steps for almost all units. The idea
being that a group of marines may all start firing together, but the minor
random +/- for the next shot will make the group look less robotic and prevent
them from firing in sync forever. This also means a fair matchup (eg 1v1 marine)
will result in a random outcome.
The update order is random and determines the order of events in a given game
loop. For example, if you have two High Templar that cast feedback on each other
on the same game loop, it will be random which one performs the damage first and
wins.
Unit auto-targeting is deterministic, but complicated. It is based on weapon
scan distance, threat and assistance. Units will consider any enemies with
weapon a higher threat than those without, and enemies that can return fire will
be a higher priority than those that can’t return fire. Units that can't return
fire (eg a missile turret vs a marine) but that are attacking an allied unit (eg
a medivac) will trigger a call for help and increase the priority. A healer
raises its priority when it is healing. If two targets have the same priority
the nearest one will be chosen.
These sources of randomness can be removed/mitigated by setting a random seed.
Replays work by saving the game setup including the random seed, as well as the
list of actions by all players, and then playing forward the simulation.
## Actions and Observations
Starcraft II has a very rich action and observation space. The game outputs
both spatial/visual and structured elements. The structured elements are given
because there is a lot of text and numbers which agents aren't expected to learn
to read, especially at low resolution. It's also because it's hard to reverse
replays back to exactly the same visuals that the human saw.
__Important Note:__
Spatial observations are in y-major screen coordinate space as `(y, x)`. Actions
that require points on the screen or the minimap, however, expect the
coordinates as `(x, y)`. The origin `(0, 0)` is at the top-left corner in both
cases.
See the [scripted agents](../pysc2/agents/scripted_agent.py) for an example of
passing a screen coordinate to an action:
```python
# Spatial observations have the y-coordinate first:
y, x = (obs.observation["feature_screen"][_PLAYER_RELATIVE] == _PLAYER_NEUTRAL).nonzero()
# Actions expect x-coordinate first:
target = [int(x.mean()), int(y.mean())]
action = actions.FunctionCall.Move_screen("now", target)
```
### Observation
#### Spatial/Visual
##### RGB Pixels
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
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 ~25 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`.
###### Minimap
The minimap is a low resolution view of the entire map. It gives an overview of
everything going on, but with less detail than the screen.
You can specify the resolution of the minimap. Maps range from
32-2562, with common human maps in the 100-2562 range.
Humans playing at 1080p get a resolution of ~2502 in the bottom left
corner of their screen.
These are the minimap feature layers:
* **height_map**: Shows the terrain levels.
* **visibility**: Which part of the map are hidden, have been seen or are
currently visible.
* **creep**: Which parts have zerg creep.
* **camera**: Which part of the map are visible in the screen layers.
* **player_id**: Who owns the units, with absolute ids.
* **player_relative**: Which units are friendly vs hostile. Takes values in
[0, 4], denoting [background, self, ally, neutral, enemy] units
respectively.
* **selected**: Which units are selected.
###### Screen
The screen is a higher resolution view of part of the map.
It is rendered from a top down orthogonal camera, as opposed to a perspective
camera that a human would get in a real game. This makes certain visuals harder
(eg you can't see elevation by size), but it also makes other things easier (eg
units don't change size as they or the camera moves). This also means that the
visible area is a rectangular part of the map, as opposed to the more
trapezoidal shape in the real game. This means there are bits of the map that an
agent can see that humans can't, and vice versa, but it is roughly similar.
A small unit (eg marine or zergling) is ~0.75 game units across. The camera is
24 game units wide. If you specify a screen resolution of 322, that
means a unit will be 1 pixel wide, meaning you have very low accuracy of its
location. If you have a large group of them you may not be able to tell how many
of them you have. A resolution of at least 642 is recommended to be
playable.
These are the screen feature layers:
* **height_map**: Shows the terrain levels.
* **visibility**: Which part of the map are hidden, have been seen or are
currently visible.
* **creep**: Which parts have zerg creep.
* **power**: Which parts have protoss power, only shows your power.
* **player_id**: Who owns the units, with absolute ids.
* **player_relative**: Which units are friendly vs hostile. Takes values in
[0, 4], denoting [background, self, ally, neutral, enemy] units
respectively.
* **unit_type**: A unit type id, which can be looked up in pysc2/lib/units.py.
* **selected**: Which units are selected.
* **hit_points**: How many hit points the unit has.
* **energy**: How much energy the unit has.
* **shields**: How much shields the unit has. Only for protoss units.
* **unit_density**: How many units are in this pixel.
* **unit_density_aa**: An anti-aliased version of unit_density with a maximum
of 16 per unit per pixel. This gives you sub-pixel unit location and size.
For example if a unit is exactly 1 pixel diameter, `unit_density` will show
it in exactly 1 pixel regardless of where in that pixel it is actually
centered. `unit_density_aa` will instead tell you how much of each pixel is
covered by the unit. A unit that is smaller than a pixel and centered in the
pixel will give a value less than the max. A unit with diameter 1 centered
near the corner of a pixel will give roughly a quarter of its value to each
of the 4 pixels it covers. If multiple units cover a pixel their proportion
of the pixel covered will be summed, up to a max of 256.
#### Structured
The game offers a fair amount of structured data which agents aren't expected
to read from pixels. Instead these are given as tensors with direct semantic
meaning.
##### General player information
A `(11)` tensor showing general information.
* player_id
* minerals
* vespene
* food used (otherwise known as supply)
* food cap
* food used by army
* food used by workers
* idle worker count
* army count
* warp gate count (for protoss)
* larva count (for zerg)
##### Control groups
A `(10, 2)` tensor showing the (unit leader type and count) for each of the 10
control groups. The indices in this tensor are referenced by the `control-group`
action.
[Control groups](http://learningsc2.com/tag/control-groups/) are a way to
remember a selection set so that you can recall them easily later.
##### Single Select
A `(7)` tensor showing information about a selected unit.
* unit type
* player_relative
* health
* shields
* energy
* transport slot taken if it's in a transport
* build progress as a percentage if it's still being built
##### Multi Select
A `(n, 7)` tensor with the same as [single select](#single-select) but for all
`n` selected units. The indices in this tensor are referenced by the
`select_unit` action.
##### Cargo
A `(n, 7)` tensor similar to [single select](#single-select), but for all the
units in a transport. The indices in this tensor are referenced by the `unload`
action.
##### Build Queue
A `(n, 7)` tensor similar to [single select](#single-select), but for all the
units being built by a production building. The indices in this tensor are
referenced by the `build_queue` action.
##### Available Actions
A `(n)` tensor listing all the action ids that are available at the time of this
observation.
##### Last Actions
A `(n)` tensor listing all the action ids that were made successfully since the
last observation. An action that was attempted but failed is not included here.
##### Action Result
A `(n)` tensor (usually size 1) giving the result of the action. The values are
listed in
[error.proto](https://github.com/Blizzard/s2client-proto/blob/master/s2clientprotocol/error.proto)
##### Alerts
A `(n)` tensor (usually empty, occasionally size 1, max 2) for when you're being attacked in a major way.
### Actions
The SC2 action space is very big. There are hundreds of possible actions, many
of which take a point in either screen or minimap space, and many of which take
an additional modifier. If you were to flatten the action space into a single
dimension, it'd have millions or even billions of possible actions, most of
which aren't valid, and many of which are highly correlated. Therefore, a flat
discrete action space is not very appropriate.
Instead, we created function actions that are rich enough to give
composability, without the complexity of an arbitrary hierarchy. This is based
on the mental model of a C-style function call which can take some arguments of
specific types. The full set of valid types and functions are defined in
`ValidActions` in `pysc2.lib.actions`, and then each observation specifies which
of the available function is valid this frame. Each action is a single
`FunctionCall` in `pysc2.lib.actions` with all its arguments filled.
The full set of types and functions are defined in `pysc2.lib.actions`. The set
of functions is hard coded and limited to just the actions that humans have
taken, as seen by a large number of replays. Hard coding the functions means
that actions created in custom maps won't be usable until they are added to
`pysc2.lib.actions`.
The semantic meaning of these actions can mainly be found by searching:
[liquipedia.net/starcraft2](http://liquipedia.net/starcraft2/) or
[starcraft.wikia](http://starcraft.wikia.com/).
#### List of actions
To see which actions exist run:
```shell
$ python -m pysc2.bin.valid_actions
```
optionally with `--hide_specific`, `--screen_resolution` or
`--minimap_resolution` if you care about the exact values. This prints something
similar to:
```
0/no_op ()
1/move_camera (1/minimap [64, 64])
2/select_point (6/select_point_act [4]; 0/screen [84, 84])
3/select_rect (7/select_add [2]; 0/screen [84, 84]; 2/screen2 [84, 84])
4/select_control_group (4/control_group_act [5]; 5/control_group_id [10])
5/select_unit (8/select_unit_act [4]; 9/select_unit_id [500])
6/select_idle_worker (10/select_worker [4])
7/select_army (7/select_add [2])
8/select_warp_gates (7/select_add [2])
9/select_larva ()
10/unload (12/unload_id [500])
11/build_queue (11/build_queue_id [10])
12/Attack_screen (3/queued [2]; 0/screen [84, 84])
13/Attack_minimap (3/queued [2]; 1/minimap [64, 64])
14/Attack_Attack_screen (3/queued [2]; 0/screen [84, 84])
19/Scan_Move_screen (3/queued [2]; 0/screen [84, 84])
23/Behavior_CloakOff_quick (3/queued [2])
26/Behavior_CloakOn_quick (3/queued [2])
42/Build_Barracks_screen (3/queued [2]; 0/screen [84, 84])
44/Build_CommandCenter_screen (3/queued [2]; 0/screen [84, 84])
220/Effect_Repair_screen (3/queued [2]; 0/screen [84, 84])
221/Effect_Repair_autocast ()
264/Harvest_Gather_screen (3/queued [2]; 0/screen [84, 84])
303/Morph_Lair_quick (3/queued [2])
317/Morph_SiegeMode_quick (3/queued [2])
322/Morph_Unsiege_quick (3/queued [2])
331/Move_screen (3/queued [2]; 0/screen [84, 84])
333/Patrol_screen (3/queued [2]; 0/screen [84, 84])
405/Research_Stimpack_quick (3/queued [2])
451/Smart_screen (3/queued [2]; 0/screen [84, 84])
452/Smart_minimap (3/queued [2]; 1/minimap [64, 64])
453/Stop_quick (3/queued [2])
477/Train_Marine_quick (3/queued [2])
*** 100s more lines ***
```
This should be read as: `/(/
[, *]; *)`.
Some examples:
* `1/move_camera (1/minimap [64, 64])` is the `move_camera` function (id `1`),
which takes one argument named `minimap` (id `1`) which requires two ints
each in the range `[0, 64)` which represent the coordinates on the minimap.
* `331/Move_screen (3/queued [2]; 0/screen [84, 84])` is the `Move_screen`
function (id `331`) which takes two arguments: `queued` (id `3`) which is a
bool and signifies whether this action should happen now or after previous
actions, and `screen` (id `0`) which takes two ints each in the range `[0,
84)` which represent a pixel on the screen.
The function names should be unique, stable and meaningful. The function and
type ids are the index into the list of `functions` and `types`.
The `types` are a predefined list of argument types that can be used in a
function call. The exact definitions are in `pysc2.lib.actions.TYPES`
#### Action categories
A `Morph` action transform a unit to a different unit, at least according to the
unit_type in the observation. For example `Morph_Lair_quick` morphs a hatchery
to a lair; `Morph_SiegeMode_quick` and `Morph_Unsiege_quick` morphs a siege tank
between tank and siege mode. An `Effect` is a single effect, rarely cancelable.
A `Behavior` can be turned on and off but doesn't change the unit type.
#### General vs Specific actions
StarCraft II speaks in terms of abilities. Sometimes a single concept that can
be done by many units is implemented as a single ability (eg Move, Halt,
Patrol), and sometimes as many abilities (eg Attack, Burrow, Cancel, Lift/Land).
`Burrow` is a simple example where each zerg unit that can burrow has its own
ability (`BurrowDown_Drone`, `BurrowDown_Zergling`, etc). Exposing those
individually would make the action space even more complicated, and make it hard
to burrow a whole army at once given a [limited APM](#apm-and-fairness), so we
added a concept of general abilities that merge all of the specific abilities
that would happen together or using the same key in the game UI. If you give the
specific ability (eg `BurrowDown_Zergling`) it'll only affect the specific units
that support that ability (eg Zerglings), while if you give the corresponding
general ability (`BurrowDown`) it'll affect all units that support it.
Attack is a non-obvious case of this. There is `Attack`, which is the general
ability that corresponds to what the UI does, but under the hood that actually
executes `Attack_Attack` for offensive units, `Scan_Move` for support units like
medivacs that can't attack but should come along as if they can,
`Attack_AttackBuilding` for defensive buildings (eg missile turret) and
`Attack_Redirect` for bunkers to tell their loaded units to attack.
For now only the general actions are exposed through the environment api so only
the general actions should be returned in the available actions observation.
In `pysc2.lib.actions.FUNCTIONS` specific functions have an additional parameter
that references the general parameter.
#### Example usage
Take a look at the [random agent](../pysc2/agents/random_agent.py) for an
example of how to consume `ValidActions` and fill `FunctionCall`s.
The following snippet shows how to print a human-readable list of available
actions:
```python
from pysc2.lib import actions
for action in obs.observation.available_actions:
print(actions.FUNCTIONS[action])
```
## RL Environment
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`.
`players` lets your specify the number and type of players. At the moment only
one or two players are supported. Give it a list of `sc2_env.Agent` or
`sc2_env.Bot` objects, specifying the race and difficulty. Specifying two agents
will start up two instances of SC2 which communicate between themselves, and
consume double the memory and cpu as playing single player.
`agent_interface_format` lets you specify the observation and action interface
to be used by each agent. `feature_dimensions` and `rgb_dimensions` 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, and slower rendering time.
If you ask for both feature and rgb observations you'll need to specify the
action space that you want to use. This lets you act in one while learning from
the other.
`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
actions of the agent (16 steps = 1 second of game time). It is equivalent to
ignoring certain observations and not sending actions on those frames, except it
also speeds up the environment since it doesn't need to render the skipped
frames.
`save_replay_episodes` and `replay_dir` specify how often to save replays and
where to save them.
Use the `run_loop.py` to have your agent interact with the environment.
### Environment wrappers
There is one pre-made environment wrapper:
* `available_actions_printer`: Prints each available action as it is seen.
## Agents
There are a couple basic agents.
* `random_agent`: Just plays randomly, shows how to make valid moves.
* `scripted_agent`: These are scripted for a single mini game.
================================================
FILE: docs/maps.md
================================================
# StarCraft II Maps
## Map config
SC2Map files are what is used by the SC2 game, but they can be used differently,
and those differences are defined in our map configs. The config gives
information like how long the episodes last, how many players it can play, and
how to score it.
To create your own map config, just subclass the base Map class and override
some of the settings. The most important is to define the directory and filename
for the SC2Map. Any Map subclass will be automatically picked up as long as it's
imported somewhere.
## DeepMind Mini-Games
The [mini-games](mini_games.md) are designed to be single-player, fixed length
and exercise different aspects of the game. They expose a score/reward which
lets the agent know how well it is doing. The score should differentiate poor
agents (eg random) from good agents.
## Ladder
[Ladder maps](http://wiki.teamliquid.net/starcraft2/Maps/Ladder_Maps/Legacy_of_the_Void)
are the maps played by human players on Battle.net. There are just a handful
active at a time. Every few months a new season starts bringing a new set of
maps.
Some of the maps have suffixes LE or TE. LE means Ladder Edition. These are
community maps that were edited by Blizzard for bugs and made ready for the
ladder pool. TE means Tournament Edition. These maps were used in tournaments.
They are all multiplayer maps with fairly long time limits.
## Melee
These are maps made specifically for machine learning. They resemble
ladder maps in format, but may be smaller sizes and aren't necessarily balanced
for high level play.
The **Flat** maps have no special features on the terrain, encouraging easy
attacking. The number specifies the map size.
The **Simple** maps are more normal with expansions, ramps, and lanes of attack,
but are smaller than normal ladder maps. The number specifies the map size.
================================================
FILE: docs/mini_games.md
================================================
# DeepMind Mini Games
## MoveToBeacon
#### Description
A map with 1 Marine and 1 Beacon. Rewards are earned by moving the marine to the
beacon. Whenever the Marine earns a reward for reaching the Beacon, the Beacon
is teleported to a random location (at least 5 units away from Marine).
#### Initial State
* 1 Marine at random location (unselected)
* 1 Beacon at random location (at least 4 units away from Marine)
#### Rewards
* Marine reaches Beacon: +1
#### End Condition
* Time elapsed
#### Time Limit
* 120 seconds
#### Additional Notes
* Fog of War disabled
* No camera movement required (single-screen)
## CollectMineralShards
#### Description
A map with 2 Marines and an endless supply of Mineral Shards. Rewards are earned
by moving the Marines to collect the Mineral Shards, with optimal collection
requiring both Marine units to be split up and moved independently. Whenever all
20 Mineral Shards have been collected, a new set of 20 Mineral Shards are
spawned at random locations (at least 2 units away from all Marines).
#### Initial State
* 2 Marines at random locations (unselected)
* 20 Mineral Shards at random locations (at least 2 units away from all
Marines)
#### Rewards
* Marine collects Mineral Shard: +1
#### End Condition
* Time elapsed
#### Time Limit
* 120 seconds
#### Additional Notes
* Fog of War disabled
* No camera movement required (single-screen)
* This is the only map in the set to require the Liberty (Campaign) mod, which
is needed for the Mineral Shard unit.
## FindAndDefeatZerglings
#### Description
A map with 3 Marines and an endless supply of stationary Zerglings. Rewards are
earned by using the Marines to defeat Zerglings, with the optimal strategy
requiring a combination of efficient exploration and combat. Whenever all 25
Zerglings have been defeated, a new set of 25 Zerglings are spawned at random
locations (at least 9 units away from all Marines and at least 5 units away from
all other Zerglings).
#### Initial State
* 3 Marines at map center (preselected)
* 2 Zerglings spawned at random locations inside player's vision range
(between 7.5 and 9.5 units away from map center and at least 5 units away
from all other Zerglings)
* 23 Zerglings spawned at random locations outside player's vision range (at
least 10.5 units away from map center and at least 5 units away from all
other Zerglings)
#### Rewards
* Zergling defeated: +1
* Marine defeated: -1
#### End Conditions
* Time elapsed
* All Marines defeated
#### Time Limit
* 180 seconds
#### Additional Notes
* Fog of War enabled
* Camera movement required (map is larger than single-screen)
## DefeatRoaches
#### Description
A map with 9 Marines and a group of 4 Roaches on opposite sides. Rewards are
earned by using the Marines to defeat Roaches, with optimal combat strategy
requiring the Marines to perform focus fire on the Roaches. Whenever all 4
Roaches have been defeated, a new group of 4 Roaches is spawned and the player
is awarded 5 additional Marines at full health, with all other surviving Marines
retaining their existing health (no restore). Whenever new units are spawned,
all unit positions are reset to opposite sides of the map.
#### Initial State
* 9 Marines in a vertical line at a random side of the map (preselected)
* 4 Roaches in a vertical line at the opposite side of the map from the
Marines
#### Rewards
* Roach defeated: +10
* Marine defeated: -1
#### End Conditions
* Time elapsed
* All Marines defeated
#### Time Limit
* 120 seconds
#### Additional Notes
* Fog of War disabled
* No camera movement required (single-screen)
* This map and DefeatZerglingsAndBanelings are currently the only maps in the
set that can include an automatic, mid-episode state change for
player-controlled units. The Marine units are automatically moved back to a
neutral position (at a random side of the map opposite the Roaches) when new
units are spawned, which occurs whenever the current set of Roaches is
defeated. This is done in order to guarantee that new units do not spawn
within combat range of one another.
## DefeatZerglingsAndBanelings
#### Description
A map with 9 Marines on the opposite side from a group of 6 Zerglings and 4
Banelings. Rewards are earned by using the Marines to defeat Zerglings and
Banelings. Whenever all Zerglings and Banelings have been defeated, a new group
of 6 Zerglings and 4 Banelings is spawned and the player is awarded 4 additional
Marines at full health, with all other surviving Marines retaining their
existing health (no restore). Whenever new units are spawned, all unit positions
are reset to opposite sides of the map.
#### Initial State
* 9 Marines in a vertical line at a random side of the map (preselected)
* 6 Zerglings and 4 Banelings in a group at the opposite side of the map from
the Marines
#### Rewards
* Zergling defeated: +5
* Baneling defeated: +5
* Marine defeated: -1
#### End Conditions
* Time elapsed
* All Marines defeated
#### Time Limit
* 120 seconds
#### Additional Notes
* Fog of War disabled
* No camera movement required (single-screen)
* This map and DefeatRoaches are currently the only maps in the set that can
include an automatic, mid-episode state change for player-controlled units.
The Marine units are automatically moved back to a neutral position (at a
random side of the map opposite the Roaches) when new units are spawned,
which occurs whenever the current set of Zerglings and Banelings is
defeated. This is done in order to guarantee that new units do not spawn
within combat range of one another.
## CollectMineralsAndGas
#### Description
A map with 12 SCVs, 1 Command Center, 16 Mineral Fields and 4 Vespene Geysers.
Rewards are based on the total amount of Minerals and Vespene Gas collected.
Spending Minerals and Vespene Gas to train new units does not decrease your
reward tally. Optimal collection will require expanding your capacity to gather
Minerals and Vespene Gas by constructing additional SCVs and an additional
Command Center.
#### Initial State
* 12 SCVs beside the Command Center (unselected)
* 1 Command Center at a fixed location
* 16 Mineral Fields at fixed locations
* 4 Vespene Geysers at fixed locations
* Player Resources: 50 Minerals, 0 Vespene, 12/15 Supply
#### Rewards
Reward total is equal to the total amount of Minerals and Vespene Gas collected
#### End Condition
Time elapsed
#### Time Limit
300 seconds
## BuildMarines
#### Description
A map with 12 SCVs, 1 Command Center, and 8 Mineral Fields. Rewards are earned
by building Marines. This is accomplished by using SCVs to collect minerals,
which are used to build Supply Depots and Barracks, which can then build
Marines.
#### Initial State
* 12 SCVs beside the Command Center (unselected)
* 1 Command Center at a fixed location
* 8 Mineral Fields at fixed locations
* Player Resources: 50 Minerals, 0 Vespene, 12/15 Supply
#### Rewards
Reward total is equal to the total number of Marines built
#### End Condition
Time elapsed
#### Time Limit
900 seconds
#### Additional Notes
* Fog of War disabled
* No camera movement required (single-screen)
* This is the only map in the set that explicitly limits the available actions
of the units to disallow actions which are not pertinent to the goal of the
map. Actions that are not required for building Marines have been removed.
================================================
FILE: pysc2/BUILD
================================================
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
licenses(["notice"])
exports_files(["LICENSE"])
bzl_library(
name = "build_defs",
srcs = ["build_defs.bzl"],
)
================================================
FILE: pysc2/__init__.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""PySC2 module: https://github.com/deepmind/pysc2 ."""
import os
def load_tests(loader, standard_tests, unused_pattern):
"""Our tests end in `_test.py`, so need to override the test discovery."""
this_dir = os.path.dirname(__file__)
package_tests = loader.discover(start_dir=this_dir, pattern="*_test.py")
standard_tests.addTests(package_tests)
return standard_tests
================================================
FILE: pysc2/agents/BUILD
================================================
load("@my_deps//:requirements.bzl", "requirement")
load("//pysc2:build_defs.bzl", "pytype_library", "pytype_strict_library")
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
py_library(
name = "agents",
srcs_version = "PY3",
deps = [
":base_agent",
":random_agent",
":scripted_agent",
],
)
pytype_library(
name = "base_agent",
srcs = ["base_agent.py"],
srcs_version = "PY3",
deps = [
"//pysc2/lib:actions",
],
)
pytype_strict_library(
name = "no_op_agent",
srcs = ["no_op_agent.py"],
deps = [
":base_agent",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_library(
name = "scripted_agent",
srcs = ["scripted_agent.py"],
srcs_version = "PY3",
deps = [
":base_agent",
requirement("numpy"),
"//pysc2/lib:actions",
"//pysc2/lib:features",
],
)
pytype_library(
name = "random_agent",
srcs = ["random_agent.py"],
srcs_version = "PY3",
deps = [
":base_agent",
requirement("numpy"),
"//pysc2/lib:actions",
],
)
================================================
FILE: pysc2/agents/__init__.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
================================================
FILE: pysc2/agents/base_agent.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""A base agent to write custom scripted agents."""
from pysc2.lib import actions
class BaseAgent(object):
"""A base agent to write custom scripted agents.
It can also act as a passive agent that does nothing but no-ops.
"""
def __init__(self):
self.reward = 0
self.episodes = 0
self.steps = 0
self.obs_spec = None
self.action_spec = None
def setup(self, obs_spec, action_spec):
self.obs_spec = obs_spec
self.action_spec = action_spec
def reset(self):
self.episodes += 1
def step(self, obs):
self.steps += 1
self.reward += obs.reward
return actions.FunctionCall(actions.FUNCTIONS.no_op.id, [])
================================================
FILE: pysc2/agents/no_op_agent.py
================================================
# Copyright 2021 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""A no-op agent for starcraft."""
from pysc2.agents import base_agent
from s2clientprotocol import sc2api_pb2 as sc_pb
class NoOpAgent(base_agent.BaseAgent):
"""A no-op agent for starcraft."""
def step(self, obs):
super(NoOpAgent, self).step(obs)
return sc_pb.Action()
================================================
FILE: pysc2/agents/random_agent.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""A random agent for starcraft."""
import numpy
from pysc2.agents import base_agent
from pysc2.lib import actions
class RandomAgent(base_agent.BaseAgent):
"""A random agent for starcraft."""
def step(self, obs):
super(RandomAgent, self).step(obs)
function_id = numpy.random.choice(obs.observation.available_actions)
args = [[numpy.random.randint(0, size) for size in arg.sizes]
for arg in self.action_spec.functions[function_id].args]
return actions.FunctionCall(function_id, args)
================================================
FILE: pysc2/agents/scripted_agent.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Scripted agents."""
import numpy
from pysc2.agents import base_agent
from pysc2.lib import actions
from pysc2.lib import features
_PLAYER_SELF = features.PlayerRelative.SELF
_PLAYER_NEUTRAL = features.PlayerRelative.NEUTRAL # beacon/minerals
_PLAYER_ENEMY = features.PlayerRelative.ENEMY
FUNCTIONS = actions.FUNCTIONS
RAW_FUNCTIONS = actions.RAW_FUNCTIONS
def _xy_locs(mask):
"""Mask should be a set of bools from comparison with a feature layer."""
y, x = mask.nonzero()
return list(zip(x, y))
class MoveToBeacon(base_agent.BaseAgent):
"""An agent specifically for solving the MoveToBeacon map."""
def step(self, obs):
super(MoveToBeacon, self).step(obs)
if FUNCTIONS.Move_screen.id in obs.observation.available_actions:
player_relative = obs.observation.feature_screen.player_relative
beacon = _xy_locs(player_relative == _PLAYER_NEUTRAL)
if not beacon:
return FUNCTIONS.no_op()
beacon_center = numpy.mean(beacon, axis=0).round()
return FUNCTIONS.Move_screen("now", beacon_center)
else:
return FUNCTIONS.select_army("select")
class CollectMineralShards(base_agent.BaseAgent):
"""An agent specifically for solving the CollectMineralShards map."""
def step(self, obs):
super(CollectMineralShards, self).step(obs)
if FUNCTIONS.Move_screen.id in obs.observation.available_actions:
player_relative = obs.observation.feature_screen.player_relative
minerals = _xy_locs(player_relative == _PLAYER_NEUTRAL)
if not minerals:
return FUNCTIONS.no_op()
marines = _xy_locs(player_relative == _PLAYER_SELF)
marine_xy = numpy.mean(marines, axis=0).round() # Average location.
distances = numpy.linalg.norm(numpy.array(minerals) - marine_xy, axis=1)
closest_mineral_xy = minerals[numpy.argmin(distances)]
return FUNCTIONS.Move_screen("now", closest_mineral_xy)
else:
return FUNCTIONS.select_army("select")
class CollectMineralShardsFeatureUnits(base_agent.BaseAgent):
"""An agent for solving the CollectMineralShards map with feature units.
Controls the two marines independently:
- select marine
- move to nearest mineral shard that wasn't the previous target
- swap marine and repeat
"""
def setup(self, obs_spec, action_spec):
super(CollectMineralShardsFeatureUnits, self).setup(obs_spec, action_spec)
if "feature_units" not in obs_spec:
raise Exception("This agent requires the feature_units observation.")
def reset(self):
super(CollectMineralShardsFeatureUnits, self).reset()
self._marine_selected = False
self._previous_mineral_xy = [-1, -1]
def step(self, obs):
super(CollectMineralShardsFeatureUnits, self).step(obs)
marines = [unit for unit in obs.observation.feature_units
if unit.alliance == _PLAYER_SELF]
if not marines:
return FUNCTIONS.no_op()
marine_unit = next((m for m in marines
if m.is_selected == self._marine_selected), marines[0])
marine_xy = [marine_unit.x, marine_unit.y]
if not marine_unit.is_selected:
# Nothing selected or the wrong marine is selected.
self._marine_selected = True
return FUNCTIONS.select_point("select", marine_xy)
if FUNCTIONS.Move_screen.id in obs.observation.available_actions:
# Find and move to the nearest mineral.
minerals = [[unit.x, unit.y] for unit in obs.observation.feature_units
if unit.alliance == _PLAYER_NEUTRAL]
if self._previous_mineral_xy in minerals:
# Don't go for the same mineral shard as other marine.
minerals.remove(self._previous_mineral_xy)
if minerals:
# Find the closest.
distances = numpy.linalg.norm(
numpy.array(minerals) - numpy.array(marine_xy), axis=1)
closest_mineral_xy = minerals[numpy.argmin(distances)]
# Swap to the other marine.
self._marine_selected = False
self._previous_mineral_xy = closest_mineral_xy
return FUNCTIONS.Move_screen("now", closest_mineral_xy)
return FUNCTIONS.no_op()
class CollectMineralShardsRaw(base_agent.BaseAgent):
"""An agent for solving CollectMineralShards with raw units and actions.
Controls the two marines independently:
- move to nearest mineral shard that wasn't the previous target
- swap marine and repeat
"""
def setup(self, obs_spec, action_spec):
super(CollectMineralShardsRaw, self).setup(obs_spec, action_spec)
if "raw_units" not in obs_spec:
raise Exception("This agent requires the raw_units observation.")
def reset(self):
super(CollectMineralShardsRaw, self).reset()
self._last_marine = None
self._previous_mineral_xy = [-1, -1]
def step(self, obs):
super(CollectMineralShardsRaw, self).step(obs)
marines = [unit for unit in obs.observation.raw_units
if unit.alliance == _PLAYER_SELF]
if not marines:
return RAW_FUNCTIONS.no_op()
marine_unit = next((m for m in marines if m.tag != self._last_marine))
marine_xy = [marine_unit.x, marine_unit.y]
minerals = [[unit.x, unit.y] for unit in obs.observation.raw_units
if unit.alliance == _PLAYER_NEUTRAL]
if self._previous_mineral_xy in minerals:
# Don't go for the same mineral shard as other marine.
minerals.remove(self._previous_mineral_xy)
if minerals:
# Find the closest.
distances = numpy.linalg.norm(
numpy.array(minerals) - numpy.array(marine_xy), axis=1)
closest_mineral_xy = minerals[numpy.argmin(distances)]
self._last_marine = marine_unit.tag
self._previous_mineral_xy = closest_mineral_xy
return RAW_FUNCTIONS.Move_pt("now", marine_unit.tag, closest_mineral_xy)
return RAW_FUNCTIONS.no_op()
class DefeatRoaches(base_agent.BaseAgent):
"""An agent specifically for solving the DefeatRoaches map."""
def step(self, obs):
super(DefeatRoaches, self).step(obs)
if FUNCTIONS.Attack_screen.id in obs.observation.available_actions:
player_relative = obs.observation.feature_screen.player_relative
roaches = _xy_locs(player_relative == _PLAYER_ENEMY)
if not roaches:
return FUNCTIONS.no_op()
# Find the roach with max y coord.
target = roaches[numpy.argmax(numpy.array(roaches)[:, 1])]
return FUNCTIONS.Attack_screen("now", target)
if FUNCTIONS.select_army.id in obs.observation.available_actions:
return FUNCTIONS.select_army("select")
return FUNCTIONS.no_op()
class DefeatRoachesRaw(base_agent.BaseAgent):
"""An agent specifically for solving DefeatRoaches using raw actions."""
def setup(self, obs_spec, action_spec):
super(DefeatRoachesRaw, self).setup(obs_spec, action_spec)
if "raw_units" not in obs_spec:
raise Exception("This agent requires the raw_units observation.")
def step(self, obs):
super(DefeatRoachesRaw, self).step(obs)
marines = [unit.tag for unit in obs.observation.raw_units
if unit.alliance == _PLAYER_SELF]
roaches = [unit for unit in obs.observation.raw_units
if unit.alliance == _PLAYER_ENEMY]
if marines and roaches:
# Find the roach with max y coord.
target = sorted(roaches, key=lambda r: r.y)[0].tag
return RAW_FUNCTIONS.Attack_unit("now", marines, target)
return FUNCTIONS.no_op()
================================================
FILE: pysc2/bin/BUILD
================================================
load("@my_deps//:requirements.bzl", "requirement")
load("//pysc2:build_defs.bzl", "pytype_binary", "pytype_strict_binary")
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
pytype_binary(
name = "replay_actions",
srcs = ["replay_actions.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:features",
"//pysc2/lib:gfile",
"//pysc2/lib:point",
"//pysc2/lib:protocol",
"//pysc2/lib:remote_controller",
"//pysc2/lib:replay",
"//pysc2/lib:static_data",
"//pysc2/maps", # build_cleaner: keep
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "gen_actions",
srcs = ["gen_actions.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:static_data",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "gen_data",
srcs = ["gen_data.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:static_data",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "check_apm",
srcs = ["check_apm.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:actions",
"//pysc2/lib:features",
"//pysc2/lib:point",
"//pysc2/lib:units",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "benchmark_observe",
srcs = ["benchmark_observe.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:replay",
"//pysc2/lib:stopwatch",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "benchmark_replay",
srcs = ["benchmark_replay.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:actions",
"//pysc2/lib:features",
"//pysc2/lib:point_flag",
"//pysc2/lib:replay",
"//pysc2/lib:stopwatch",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "mem_leak_check",
srcs = ["mem_leak_check.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"@absl_py//absl:app",
"@absl_py//absl/flags",
requirement("psutil"), # build_cleaner: keep
"//pysc2/lib:protocol",
"//pysc2/maps",
"//pysc2/run_configs",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "gen_versions",
srcs = ["gen_versions.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"@absl_py//absl:app",
requirement("requests"),
],
)
pytype_binary(
name = "map_list",
srcs = ["map_list.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/maps",
"@absl_py//absl:app",
],
)
pytype_binary(
name = "play",
srcs = ["play.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/env:sc2_env",
"//pysc2/lib:point_flag",
"//pysc2/lib:renderer_human",
"//pysc2/lib:replay",
"//pysc2/lib:stopwatch",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "play_vs_agent",
srcs = ["play_vs_agent.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@absl_py//absl/logging",
requirement("portpicker"),
"//pysc2/agents", # build_cleaner: keep
"//pysc2/env:lan_sc2_env",
"//pysc2/env:run_loop",
"//pysc2/env:sc2_env",
"//pysc2/lib:point_flag",
"//pysc2/lib:renderer_human",
"//pysc2/maps",
"//pysc2/run_configs",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "agent_remote",
srcs = ["agent_remote.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/agents", # build_cleaner: keep
"//pysc2/env:remote_sc2_env",
"//pysc2/env:run_loop",
"//pysc2/env:sc2_env",
"//pysc2/lib:point_flag",
"//pysc2/lib:portspicker",
"//pysc2/lib:renderer_human",
"//pysc2/maps",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@absl_py//absl/logging",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "replay_info",
srcs = ["replay_info.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:gfile",
"//pysc2/lib:remote_controller",
"//pysc2/lib:replay",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:common_py_pb2",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_binary(
name = "agent",
srcs = ["agent.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/agents", # build_cleaner: keep
"//pysc2/env:available_actions_printer",
"//pysc2/env:run_loop",
"//pysc2/env:sc2_env",
"//pysc2/lib:point_flag",
"//pysc2/lib:stopwatch",
"//pysc2/maps",
"@absl_py//absl:app",
"@absl_py//absl/flags",
],
)
pytype_binary(
name = "valid_actions",
srcs = ["valid_actions.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:actions",
"//pysc2/lib:features",
"//pysc2/lib:point_flag",
"@absl_py//absl:app",
"@absl_py//absl/flags",
],
)
pytype_binary(
name = "compare_binaries",
srcs = ["compare_binaries.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:image_differencer",
"//pysc2/lib:proto_diff",
"//pysc2/lib:remote_controller",
"//pysc2/lib:replay",
"//pysc2/lib:stopwatch",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_strict_binary(
name = "battle_net_maps",
srcs = ["battle_net_maps.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/run_configs",
"@absl_py//absl:app",
],
)
pytype_strict_binary(
name = "reencode_replays",
srcs = ["reencode_replays.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"//pysc2/lib:replay",
"//pysc2/run_configs",
"@absl_py//absl:app",
"@absl_py//absl/flags",
"@s2client_proto//s2clientprotocol:sc2api_py_pb2",
],
)
pytype_strict_binary(
name = "update_battle_net_cache",
srcs = ["update_battle_net_cache.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [
"@absl_py//absl:app",
"@absl_py//absl/flags",
requirement("mpyq"),
"//pysc2/run_configs",
"@s2protocol_archive//:versions",
],
)
pytype_strict_binary(
name = "replay_version",
srcs = ["replay_version.py"],
deps = [
"//pysc2/lib:replay",
"//pysc2/run_configs",
"@absl_py//absl:app",
],
)
================================================
FILE: pysc2/bin/__init__.py
================================================
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
================================================
FILE: pysc2/bin/agent.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Run an agent."""
import importlib
import threading
from absl import app
from absl import flags
from pysc2 import maps
from pysc2.env import available_actions_printer
from pysc2.env import run_loop
from pysc2.env import sc2_env
from pysc2.lib import point_flag
from pysc2.lib import stopwatch
FLAGS = flags.FLAGS
flags.DEFINE_bool("render", True, "Whether to render with pygame.")
point_flag.DEFINE_point("feature_screen_size", "84",
"Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
"Resolution for minimap feature layers.")
point_flag.DEFINE_point("rgb_screen_size", None,
"Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", None,
"Resolution for rendered minimap.")
flags.DEFINE_enum("action_space", None, sc2_env.ActionSpace._member_names_, # pylint: disable=protected-access
"Which action space to use. Needed if you take both feature "
"and rgb observations.")
flags.DEFINE_bool("use_feature_units", False,
"Whether to include feature units.")
flags.DEFINE_bool("use_raw_units", False,
"Whether to include raw units.")
flags.DEFINE_bool("disable_fog", False, "Whether to disable Fog of War.")
flags.DEFINE_integer("max_agent_steps", 0, "Total agent steps.")
flags.DEFINE_integer("game_steps_per_episode", None, "Game steps per episode.")
flags.DEFINE_integer("max_episodes", 0, "Total episodes.")
flags.DEFINE_integer("step_mul", 8, "Game steps per agent step.")
flags.DEFINE_string("agent", "pysc2.agents.random_agent.RandomAgent",
"Which agent to run, as a python path to an Agent class.")
flags.DEFINE_string("agent_name", None,
"Name of the agent in replays. Defaults to the class name.")
flags.DEFINE_enum("agent_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"Agent 1's race.")
flags.DEFINE_string("agent2", "Bot", "Second agent, either Bot or agent class.")
flags.DEFINE_string("agent2_name", None,
"Name of the agent in replays. Defaults to the class name.")
flags.DEFINE_enum("agent2_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"Agent 2's race.")
flags.DEFINE_enum("difficulty", "very_easy", sc2_env.Difficulty._member_names_, # pylint: disable=protected-access
"If agent2 is a built-in Bot, it's strength.")
flags.DEFINE_enum("bot_build", "random", sc2_env.BotBuild._member_names_, # pylint: disable=protected-access
"Bot's build strategy.")
flags.DEFINE_bool("profile", False, "Whether to turn on code profiling.")
flags.DEFINE_bool("trace", False, "Whether to trace the code execution.")
flags.DEFINE_integer("parallel", 1, "How many instances to run in parallel.")
flags.DEFINE_bool("save_replay", True, "Whether to save a replay at the end.")
flags.DEFINE_string("map", None, "Name of a map to use.")
flags.DEFINE_bool("battle_net_map", False, "Use the battle.net map version.")
flags.mark_flag_as_required("map")
def run_thread(agent_classes, players, map_name, visualize):
"""Run one thread worth of the environment with agents."""
with sc2_env.SC2Env(
map_name=map_name,
battle_net_map=FLAGS.battle_net_map,
players=players,
agent_interface_format=sc2_env.parse_agent_interface_format(
feature_screen=FLAGS.feature_screen_size,
feature_minimap=FLAGS.feature_minimap_size,
rgb_screen=FLAGS.rgb_screen_size,
rgb_minimap=FLAGS.rgb_minimap_size,
action_space=FLAGS.action_space,
use_feature_units=FLAGS.use_feature_units,
use_raw_units=FLAGS.use_raw_units),
step_mul=FLAGS.step_mul,
game_steps_per_episode=FLAGS.game_steps_per_episode,
disable_fog=FLAGS.disable_fog,
visualize=visualize) as env:
env = available_actions_printer.AvailableActionsPrinter(env)
agents = [agent_cls() for agent_cls in agent_classes]
run_loop.run_loop(agents, env, FLAGS.max_agent_steps, FLAGS.max_episodes)
if FLAGS.save_replay:
env.save_replay(agent_classes[0].__name__)
def main(unused_argv):
"""Run an agent."""
if FLAGS.trace:
stopwatch.sw.trace()
elif FLAGS.profile:
stopwatch.sw.enable()
map_inst = maps.get(FLAGS.map)
agent_classes = []
players = []
agent_module, agent_name = FLAGS.agent.rsplit(".", 1)
agent_cls = getattr(importlib.import_module(agent_module), agent_name)
agent_classes.append(agent_cls)
players.append(sc2_env.Agent(sc2_env.Race[FLAGS.agent_race],
FLAGS.agent_name or agent_name))
if map_inst.players >= 2:
if FLAGS.agent2 == "Bot":
players.append(sc2_env.Bot(sc2_env.Race[FLAGS.agent2_race],
sc2_env.Difficulty[FLAGS.difficulty],
sc2_env.BotBuild[FLAGS.bot_build]))
else:
agent_module, agent_name = FLAGS.agent2.rsplit(".", 1)
agent_cls = getattr(importlib.import_module(agent_module), agent_name)
agent_classes.append(agent_cls)
players.append(sc2_env.Agent(sc2_env.Race[FLAGS.agent2_race],
FLAGS.agent2_name or agent_name))
threads = []
for _ in range(FLAGS.parallel - 1):
t = threading.Thread(target=run_thread,
args=(agent_classes, players, FLAGS.map, False))
threads.append(t)
t.start()
run_thread(agent_classes, players, FLAGS.map, FLAGS.render)
for t in threads:
t.join()
if FLAGS.profile:
print(stopwatch.sw)
def entry_point(): # Needed so setup.py scripts work.
app.run(main)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/agent_remote.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
r"""Play an agent with an SC2 instance that isn't owned.
This can be used to play on the sc2ai.net ladder, as well as to play vs humans.
To play on ladder:
$ python -m pysc2.bin.agent_remote --agent \
--host_port --lan_port
To play vs humans:
$ python -m pysc2.bin.agent_remote --human --map
then copy the string it generates which is something similar to above
If you want to play remotely, you'll need to port forward (eg with ssh -L or -R)
the host_port from localhost on one machine to localhost on the other.
You can also set your race, observation options, etc by cmdline flags.
When playing vs humans it launches both instances on the human side. This means
you only need to port-forward a single port (ie the websocket betwen SC2 and the
agent), but you also need to transfer the entire observation, which is much
bigger than the actions transferred over the lan connection between the two SC2
instances. It also makes it easy to maintain version compatibility since they
are the same binary. Unfortunately it means higher cpu usage where the human is
playing, which on a Mac becomes problematic as OSX slows down the instance
running in the background. There can also be observation differences between
Mac/Win and Linux. For these reasons, prefer play_vs_agent which runs the
instance next to the agent, and tunnels the lan actions instead.
"""
import getpass
import importlib
import platform
import sys
import time
from absl import app
from absl import flags
from absl import logging
from pysc2 import maps
from pysc2 import run_configs
from pysc2.env import remote_sc2_env
from pysc2.env import run_loop
from pysc2.env import sc2_env
from pysc2.lib import point_flag
from pysc2.lib import portspicker
from pysc2.lib import renderer_human
from s2clientprotocol import sc2api_pb2 as sc_pb
FLAGS = flags.FLAGS
flags.DEFINE_bool("render", platform.system() == "Linux",
"Whether to render with pygame.")
flags.DEFINE_bool("realtime", False, "Whether to run in realtime mode.")
flags.DEFINE_string("agent", "pysc2.agents.random_agent.RandomAgent",
"Which agent to run, as a python path to an Agent class.")
flags.DEFINE_string("agent_name", None,
"Name of the agent in replays. Defaults to the class name.")
flags.DEFINE_enum("agent_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"Agent's race.")
flags.DEFINE_float("fps", 22.4, "Frames per second to run the game.")
flags.DEFINE_integer("step_mul", 8, "Game steps per agent step.")
point_flag.DEFINE_point("feature_screen_size", "84",
"Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
"Resolution for minimap feature layers.")
point_flag.DEFINE_point("rgb_screen_size", "256",
"Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", "128",
"Resolution for rendered minimap.")
flags.DEFINE_enum("action_space", "FEATURES",
sc2_env.ActionSpace._member_names_, # pylint: disable=protected-access
"Which action space to use. Needed if you take both feature "
"and rgb observations.")
flags.DEFINE_bool("use_feature_units", False,
"Whether to include feature units.")
flags.DEFINE_string("user_name", getpass.getuser(),
"Name of the human player for replays.")
flags.DEFINE_enum("user_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"User's race.")
flags.DEFINE_string("host", "127.0.0.1", "Game Host")
flags.DEFINE_integer("host_port", None, "Host port")
flags.DEFINE_integer("lan_port", None, "Host port")
flags.DEFINE_string("map", None, "Name of a map to use to play.")
flags.DEFINE_bool("human", False, "Whether to host a game as a human.")
flags.DEFINE_integer("timeout_seconds", 300,
"Time in seconds for the remote agent to connect to the "
"game before an exception is raised.")
def main(unused_argv):
if FLAGS.human:
human()
else:
agent()
def agent():
"""Run the agent, connecting to a (remote) host started independently."""
agent_module, agent_name = FLAGS.agent.rsplit(".", 1)
agent_cls = getattr(importlib.import_module(agent_module), agent_name)
logging.info("Starting agent:")
with remote_sc2_env.RemoteSC2Env(
map_name=FLAGS.map,
host=FLAGS.host,
host_port=FLAGS.host_port,
lan_port=FLAGS.lan_port,
name=FLAGS.agent_name or agent_name,
race=sc2_env.Race[FLAGS.agent_race],
step_mul=FLAGS.step_mul,
agent_interface_format=sc2_env.parse_agent_interface_format(
feature_screen=FLAGS.feature_screen_size,
feature_minimap=FLAGS.feature_minimap_size,
rgb_screen=FLAGS.rgb_screen_size,
rgb_minimap=FLAGS.rgb_minimap_size,
action_space=FLAGS.action_space,
use_feature_units=FLAGS.use_feature_units),
visualize=FLAGS.render) as env:
agents = [agent_cls()]
logging.info("Connected, starting run_loop.")
try:
run_loop.run_loop(agents, env)
except remote_sc2_env.RestartError:
pass
logging.info("Done.")
def human():
"""Run a host which expects one player to connect remotely."""
run_config = run_configs.get()
map_inst = maps.get(FLAGS.map)
if not FLAGS.rgb_screen_size or not FLAGS.rgb_minimap_size:
logging.info("Use --rgb_screen_size and --rgb_minimap_size if you want rgb "
"observations.")
ports = portspicker.pick_contiguous_unused_ports(4) # 2 * num_players
host_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
timeout_seconds=FLAGS.timeout_seconds,
window_loc=(50, 50))
client_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
connect=False, window_loc=(700, 50))
create = sc_pb.RequestCreateGame(
realtime=FLAGS.realtime, local_map=sc_pb.LocalMap(map_path=map_inst.path))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Participant)
controller = host_proc.controller
controller.save_map(map_inst.path, map_inst.data(run_config))
controller.create_game(create)
print("-" * 80)
print("Join host: agent_remote --map %s --host %s --host_port %s "
"--lan_port %s" % (FLAGS.map, FLAGS.host, client_proc.port, ports[0]))
print("-" * 80)
sys.stdout.flush()
join = sc_pb.RequestJoinGame()
join.shared_port = 0 # unused
join.server_ports.game_port = ports.pop(0)
join.server_ports.base_port = ports.pop(0)
join.client_ports.add(game_port=ports.pop(0), base_port=ports.pop(0))
join.race = sc2_env.Race[FLAGS.user_race]
join.player_name = FLAGS.user_name
if FLAGS.render:
join.options.raw = True
join.options.score = True
if FLAGS.feature_screen_size and FLAGS.feature_minimap_size:
fl = join.options.feature_layer
fl.width = 24
FLAGS.feature_screen_size.assign_to(fl.resolution)
FLAGS.feature_minimap_size.assign_to(fl.minimap_resolution)
if FLAGS.rgb_screen_size and FLAGS.rgb_minimap_size:
FLAGS.rgb_screen_size.assign_to(join.options.render.resolution)
FLAGS.rgb_minimap_size.assign_to(join.options.render.minimap_resolution)
controller.join_game(join)
if FLAGS.render:
renderer = renderer_human.RendererHuman(
fps=FLAGS.fps, render_feature_grid=False)
renderer.run(run_configs.get(), controller, max_episodes=1)
else: # Still step forward so the Mac/Windows renderer works.
try:
while True:
frame_start_time = time.time()
if not FLAGS.realtime:
controller.step()
obs = controller.observe()
if obs.player_result:
break
time.sleep(max(0, frame_start_time - time.time() + 1 / FLAGS.fps))
except KeyboardInterrupt:
pass
for p in [host_proc, client_proc]:
p.close()
portspicker.return_ports(ports)
def entry_point(): # Needed so setup.py scripts work.
app.run(main)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/battle_net_maps.py
================================================
#!/usr/bin/python
# Copyright 2019 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Print the list of available maps according to the game."""
from absl import app
from pysc2 import run_configs
def main(unused_argv):
with run_configs.get().start(want_rgb=False) as controller:
available_maps = controller.available_maps()
print("\n")
print("Local map paths:")
for m in sorted(available_maps.local_map_paths):
print(" ", m)
print()
print("Battle.net maps:")
for m in sorted(available_maps.battlenet_map_names):
print(" ", m)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/benchmark_observe.py
================================================
#!/usr/bin/python
# Copyright 2018 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Benchmark observation times."""
import time
from absl import app
from absl import flags
from pysc2 import maps
from pysc2 import run_configs
from pysc2.lib import replay
from pysc2.lib import stopwatch
from s2clientprotocol import common_pb2 as sc_common
from s2clientprotocol import sc2api_pb2 as sc_pb
flags.DEFINE_integer("count", 1000, "How many observations to run.")
flags.DEFINE_integer("step_mul", 16, "How many game steps per observation.")
flags.DEFINE_string("replay", None, "Which replay to run.")
flags.DEFINE_string("map", "Catalyst", "Which map to run.")
FLAGS = flags.FLAGS
def interface_options(score=False, raw=False, features=None, rgb=None,
crop=True):
"""Get an InterfaceOptions for the config."""
interface = sc_pb.InterfaceOptions()
interface.score = score
interface.raw = raw
if features:
if isinstance(features, int):
screen, minimap = features, features
else:
screen, minimap = features
interface.feature_layer.width = 24
interface.feature_layer.resolution.x = screen
interface.feature_layer.resolution.y = screen
interface.feature_layer.minimap_resolution.x = minimap
interface.feature_layer.minimap_resolution.y = minimap
interface.feature_layer.crop_to_playable_area = crop
if rgb:
if isinstance(rgb, int):
screen, minimap = rgb, rgb
else:
screen, minimap = rgb
interface.render.resolution.x = screen
interface.render.resolution.y = screen
interface.render.minimap_resolution.x = minimap
interface.render.minimap_resolution.y = minimap
return interface
configs = [
("raw", interface_options(raw=True)),
("raw-feat-48", interface_options(raw=True, features=48)),
("raw-feat-128", interface_options(raw=True, features=128)),
("raw-feat-128-48", interface_options(raw=True, features=(128, 48))),
("feat-32", interface_options(features=32)),
("feat-48", interface_options(features=48)),
("feat-72-no-crop", interface_options(features=72, crop=False)),
("feat-72", interface_options(features=72)),
("feat-96", interface_options(features=96)),
("feat-128", interface_options(features=128)),
("rgb-64", interface_options(rgb=64)),
("rgb-128", interface_options(rgb=128)),
]
def main(unused_argv):
stopwatch.sw.enable()
results = []
try:
for config, interface in configs:
print((" Starting: %s " % config).center(60, "-"))
timeline = []
run_config = run_configs.get()
if FLAGS.replay:
replay_data = run_config.replay_data(FLAGS.replay)
start_replay = sc_pb.RequestStartReplay(
replay_data=replay_data, options=interface, disable_fog=False,
observed_player_id=2)
version = replay.get_replay_version(replay_data)
run_config = run_configs.get(version=version) # Replace the run config.
else:
map_inst = maps.get(FLAGS.map)
create = sc_pb.RequestCreateGame(
realtime=False, disable_fog=False, random_seed=1,
local_map=sc_pb.LocalMap(map_path=map_inst.path,
map_data=map_inst.data(run_config)))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer, race=sc_common.Terran,
difficulty=sc_pb.VeryEasy)
join = sc_pb.RequestJoinGame(options=interface, race=sc_common.Protoss)
with run_config.start(
want_rgb=interface.HasField("render")) as controller:
if FLAGS.replay:
info = controller.replay_info(replay_data)
print(" Replay info ".center(60, "-"))
print(info)
print("-" * 60)
if info.local_map_path:
start_replay.map_data = run_config.map_data(info.local_map_path)
controller.start_replay(start_replay)
else:
controller.create_game(create)
controller.join_game(join)
for _ in range(FLAGS.count):
controller.step(FLAGS.step_mul)
start = time.time()
obs = controller.observe()
timeline.append(time.time() - start)
if obs.player_result:
break
results.append((config, timeline))
except KeyboardInterrupt:
pass
names, values = zip(*results)
print("\n\nTimeline:\n")
print(",".join(names))
for times in zip(*values):
print(",".join("%0.2f" % (t * 1000) for t in times))
print(stopwatch.sw)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/benchmark_replay.py
================================================
#!/usr/bin/python
# Copyright 2018 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Benchmark observation times."""
from absl import app
from absl import flags
from pysc2 import run_configs
from pysc2.lib import actions
from pysc2.lib import features
from pysc2.lib import point_flag
from pysc2.lib import replay
from pysc2.lib import stopwatch
from s2clientprotocol import sc2api_pb2 as sc_pb
FLAGS = flags.FLAGS
flags.DEFINE_integer("step_mul", 8, "Game steps per observation.")
point_flag.DEFINE_point("feature_screen_size", "64",
"Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
"Resolution for minimap feature layers.")
point_flag.DEFINE_point("rgb_screen_size", None,
"Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", None,
"Resolution for rendered minimap.")
flags.DEFINE_bool("use_feature_units", True,
"Whether to include feature units.")
flags.DEFINE_bool("use_raw_units", True,
"Whether to include raw units.")
flags.DEFINE_string("replay", None, "Name of a replay to show.")
flags.DEFINE_string("map_path", None, "Override the map for this replay.")
flags.mark_flag_as_required("replay")
def main(argv):
if len(argv) > 1:
raise app.UsageError("Too many command-line arguments.")
stopwatch.sw.enable()
interface = sc_pb.InterfaceOptions()
interface.raw = FLAGS.use_feature_units or FLAGS.use_raw_units
interface.score = True
interface.feature_layer.width = 24
if FLAGS.feature_screen_size and FLAGS.feature_minimap_size:
FLAGS.feature_screen_size.assign_to(interface.feature_layer.resolution)
FLAGS.feature_minimap_size.assign_to(
interface.feature_layer.minimap_resolution)
if FLAGS.rgb_screen_size and FLAGS.rgb_minimap_size:
FLAGS.rgb_screen_size.assign_to(interface.render.resolution)
FLAGS.rgb_minimap_size.assign_to(interface.render.minimap_resolution)
run_config = run_configs.get()
replay_data = run_config.replay_data(FLAGS.replay)
start_replay = sc_pb.RequestStartReplay(
replay_data=replay_data,
options=interface,
observed_player_id=1)
version = replay.get_replay_version(replay_data)
run_config = run_configs.get(version=version) # Replace the run config.
try:
with run_config.start(
want_rgb=interface.HasField("render")) as controller:
info = controller.replay_info(replay_data)
print(" Replay info ".center(60, "-"))
print(info)
print("-" * 60)
map_path = FLAGS.map_path or info.local_map_path
if map_path:
start_replay.map_data = run_config.map_data(map_path)
controller.start_replay(start_replay)
feats = features.features_from_game_info(
game_info=controller.game_info(),
use_feature_units=FLAGS.use_feature_units,
use_raw_units=FLAGS.use_raw_units,
use_unit_counts=interface.raw,
use_camera_position=False,
action_space=actions.ActionSpace.FEATURES)
while True:
controller.step(FLAGS.step_mul)
obs = controller.observe()
feats.transform_obs(obs)
if obs.player_result:
break
except KeyboardInterrupt:
pass
print(stopwatch.sw)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/check_apm.py
================================================
# Copyright 2018 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Test the apm values of various actions."""
import random
from absl import app
from pysc2 import maps
from pysc2 import run_configs
from pysc2.lib import actions
from pysc2.lib import features
from pysc2.lib import point
from pysc2.lib import units
from s2clientprotocol import common_pb2 as sc_common
from s2clientprotocol import error_pb2 as sc_error
from s2clientprotocol import sc2api_pb2 as sc_pb
def get_units(obs, filter_fn=None, owner=None, unit_type=None, tag=None):
"""Return a dict of units that match the filter."""
if unit_type and not isinstance(unit_type, (list, tuple)):
unit_type = (unit_type,)
return {u.tag: u for u in obs.observation.raw_data.units # pylint: disable=g-complex-comprehension
if ((filter_fn is None or filter_fn(u)) and
(owner is None or u.owner == owner) and
(unit_type is None or u.unit_type in unit_type) and
(tag is None or u.tag == tag))}
def get_unit(*args, **kwargs):
"""Return the first unit that matches, or None."""
try:
return next(iter(get_units(*args, **kwargs).values()))
except StopIteration:
return None
def _xy_locs(mask):
"""Mask should be a set of bools from comparison with a feature layer."""
ys, xs = mask.nonzero()
return [point.Point(x, y) for x, y in zip(xs, ys)]
class Env(object):
"""Test the apm values of various actions."""
def __init__(self):
run_config = run_configs.get()
map_inst = maps.get("Flat64")
self._map_path = map_inst.path
self._map_data = map_inst.data(run_config)
self._sc2_proc = run_config.start(want_rgb=False)
self._controller = self._sc2_proc.controller
self._summary = []
self._features = None
def close(self):
self._controller.quit()
self._sc2_proc.close()
print(" apm name")
for name, info in self._summary:
print("%4d %s" % (info.player_info[0].player_apm, name))
def __enter__(self):
return self
def __exit__(self, unused_exception_type, unused_exc_value, unused_traceback):
self.close()
def __del__(self):
self.close()
def step(self, count=22):
self._controller.step(count)
return self._controller.observe()
def fl_obs(self, obs):
return self._features.transform_obs(obs)
def raw_unit_command(self, ability_id, unit_tags, pos=None, target=None):
"""Send a raw unit command."""
if isinstance(ability_id, str):
ability_id = actions.FUNCTIONS[ability_id].ability_id
action = sc_pb.Action()
cmd = action.action_raw.unit_command
cmd.ability_id = ability_id
if isinstance(unit_tags, (list, tuple)):
cmd.unit_tags.extend(unit_tags)
else:
cmd.unit_tags.append(unit_tags)
if pos:
cmd.target_world_space_pos.x = pos[0]
cmd.target_world_space_pos.y = pos[1]
elif target:
cmd.target_unit_tag = target
response = self._controller.act(action)
for result in response.result:
assert result == sc_error.Success
def fl_action(self, obs, act, *args):
return self._controller.act(self._features.transform_action(
obs.observation, actions.FUNCTIONS[act](*args), skip_available=True))
def check_apm(self, name):
"""Set up a game, yield, then check the apm in the replay."""
interface = sc_pb.InterfaceOptions(raw=True, score=False)
interface.feature_layer.width = 24
interface.feature_layer.resolution.x = 64
interface.feature_layer.resolution.y = 64
interface.feature_layer.minimap_resolution.x = 64
interface.feature_layer.minimap_resolution.y = 64
create = sc_pb.RequestCreateGame(
random_seed=1, local_map=sc_pb.LocalMap(map_path=self._map_path,
map_data=self._map_data))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer, race=sc_common.Protoss,
difficulty=sc_pb.VeryEasy)
join = sc_pb.RequestJoinGame(race=sc_common.Protoss, options=interface)
self._controller.create_game(create)
self._controller.join_game(join)
self._info = self._controller.game_info()
self._features = features.features_from_game_info(
self._info, use_feature_units=True, use_raw_units=True)
self._map_size = point.Point.build(self._info.start_raw.map_size)
for i in range(60):
yield i, self.step()
data = self._controller.save_replay()
replay_info = self._controller.replay_info(data)
self._summary.append((name, replay_info))
def main(unused_argv):
env = Env()
def rand_fl_coord():
return (point.Point.unit_rand() * 64).floor()
def rand_world_coord():
return (point.Point.unit_rand() * 20).floor() + 20
def random_probe_loc(obs):
return random.choice(_xy_locs(
env.fl_obs(obs).feature_screen.unit_type == units.Protoss.Probe))
def random_probe_tag(obs):
return random.choice(get_units(obs, unit_type=units.Protoss.Probe).keys())
for i, obs in env.check_apm("no-op"):
pass
for i, obs in env.check_apm("fl stop, single probe"):
if i == 0:
env.fl_action(obs, "select_point", "select", random_probe_loc(obs))
env.fl_action(obs, "Stop_quick", "now")
for i, obs in env.check_apm("fl smart, single probe, random location"):
if i == 0:
env.fl_action(obs, "select_point", "select", random_probe_loc(obs))
env.fl_action(obs, "Smart_screen", "now", rand_fl_coord())
for i, obs in env.check_apm("fl move, single probe, random location"):
if i == 0:
env.fl_action(obs, "select_point", "select", random_probe_loc(obs))
env.fl_action(obs, "Move_screen", "now", rand_fl_coord())
for i, obs in env.check_apm("fl stop, random probe"):
env.fl_action(obs, "select_point", "select", random_probe_loc(obs))
env.fl_action(obs, "Stop_quick", "now")
for i, obs in env.check_apm("fl move, random probe, random location"):
env.fl_action(obs, "select_point", "select", random_probe_loc(obs))
env.fl_action(obs, "Move_screen", "now", rand_fl_coord())
for i, obs in env.check_apm("raw stop, random probe"):
env.raw_unit_command("Stop_quick", random_probe_tag(obs))
for i, obs in env.check_apm("raw move, random probe, random location"):
env.raw_unit_command("Move_screen", random_probe_tag(obs),
rand_world_coord())
probe = None
for i, obs in env.check_apm("raw move, single probe, random location"):
if not probe:
probe = random_probe_tag(obs)
env.raw_unit_command("Move_screen", probe, rand_world_coord())
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/compare_binaries.py
================================================
#!/usr/bin/python
# Copyright 2019 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Compare the observations from multiple binaries."""
import collections
import sys
from absl import app
from absl import flags
from pysc2 import run_configs
from pysc2.lib import image_differencer
from pysc2.lib import proto_diff
from pysc2.lib import remote_controller
from pysc2.lib import replay
from pysc2.lib import stopwatch
from s2clientprotocol import sc2api_pb2 as sc_pb
FLAGS = flags.FLAGS
flags.DEFINE_bool("diff", False, "Whether to diff the observations.")
flags.DEFINE_integer("truncate", 0,
"Number of characters to truncate diff values to, or 0 "
"for no truncation.")
flags.DEFINE_integer("step_mul", 8, "Game steps per observation.")
flags.DEFINE_integer("count", 100000, "How many observations to run.")
flags.DEFINE_string("replay", None, "Name of a replay to show.")
def _clear_non_deterministic_fields(obs):
for unit in obs.observation.raw_data.units:
unit.ClearField("tag")
for order in unit.orders:
order.ClearField("target_unit_tag")
for action in obs.actions:
if action.HasField("action_raw"):
if action.action_raw.HasField("unit_command"):
action.action_raw.unit_command.ClearField("target_unit_tag")
def _is_remote(arg):
return ":" in arg
def main(argv):
"""Compare the observations from multiple binaries."""
if len(argv) <= 1:
sys.exit(
"Please specify binaries to run / to connect to. For binaries to run, "
"specify the executable name. For remote connections, specify "
":. The version must match the replay.")
targets = argv[1:]
interface = sc_pb.InterfaceOptions()
interface.raw = True
interface.raw_affects_selection = True
interface.raw_crop_to_playable_area = True
interface.score = True
interface.show_cloaked = True
interface.show_placeholders = True
interface.feature_layer.width = 24
interface.feature_layer.resolution.x = 48
interface.feature_layer.resolution.y = 48
interface.feature_layer.minimap_resolution.x = 48
interface.feature_layer.minimap_resolution.y = 48
interface.feature_layer.crop_to_playable_area = True
interface.feature_layer.allow_cheating_layers = True
run_config = run_configs.get()
replay_data = run_config.replay_data(FLAGS.replay)
start_replay = sc_pb.RequestStartReplay(
replay_data=replay_data,
options=interface,
observed_player_id=1,
realtime=False)
version = replay.get_replay_version(replay_data)
timers = []
controllers = []
procs = []
for target in targets:
timer = stopwatch.StopWatch()
timers.append(timer)
with timer("launch"):
if _is_remote(target):
host, port = target.split(":")
controllers.append(remote_controller.RemoteController(host, int(port)))
else:
proc = run_configs.get(
version=version._replace(binary=target)).start(want_rgb=False)
procs.append(proc)
controllers.append(proc.controller)
diff_counts = [0] * len(controllers)
diff_paths = collections.Counter()
try:
print("-" * 80)
print(controllers[0].replay_info(replay_data))
print("-" * 80)
for controller, t in zip(controllers, timers):
with t("start_replay"):
controller.start_replay(start_replay)
# Check the static data.
static_data = []
for controller, t in zip(controllers, timers):
with t("data"):
static_data.append(controller.data_raw())
if FLAGS.diff:
diffs = {i: proto_diff.compute_diff(static_data[0], d)
for i, d in enumerate(static_data[1:], 1)}
if any(diffs.values()):
print(" Diff in static data ".center(80, "-"))
for i, diff in diffs.items():
if diff:
print(targets[i])
diff_counts[i] += 1
print(diff.report(truncate_to=FLAGS.truncate))
for path in diff.all_diffs():
diff_paths[path.with_anonymous_array_indices()] += 1
else:
print("No diffs in static data.")
# Run some steps, checking speed and diffing the observations.
for _ in range(FLAGS.count):
for controller, t in zip(controllers, timers):
with t("step"):
controller.step(FLAGS.step_mul)
obs = []
for controller, t in zip(controllers, timers):
with t("observe"):
obs.append(controller.observe())
if FLAGS.diff:
for o in obs:
_clear_non_deterministic_fields(o)
diffs = {i: proto_diff.compute_diff(obs[0], o)
for i, o in enumerate(obs[1:], 1)}
if any(diffs.values()):
print((" Diff on step: %s " %
obs[0].observation.game_loop).center(80, "-"))
for i, diff in diffs.items():
if diff:
print(targets[i])
diff_counts[i] += 1
print(diff.report([image_differencer.image_differencer],
truncate_to=FLAGS.truncate))
for path in diff.all_diffs():
diff_paths[path.with_anonymous_array_indices()] += 1
if obs[0].player_result:
break
except KeyboardInterrupt:
pass
finally:
for c in controllers:
c.quit()
c.close()
for p in procs:
p.close()
if FLAGS.diff:
print(" Diff Counts by target ".center(80, "-"))
for target, count in zip(targets, diff_counts):
print(" %5d %s" % (count, target))
print()
print(" Diff Counts by observation path ".center(80, "-"))
for path, count in diff_paths.most_common(100):
print(" %5d %s" % (count, path))
print()
print(" Timings ".center(80, "-"))
for v, t in zip(targets, timers):
print(v)
print(t)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/gen_actions.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Generate the action definitions for actions.py."""
import itertools
from absl import app
from absl import flags
from pysc2 import maps
from pysc2 import run_configs
from pysc2.lib import static_data
from s2clientprotocol import common_pb2 as sc_common
from s2clientprotocol import data_pb2 as sc_data
from s2clientprotocol import sc2api_pb2 as sc_pb
flags.DEFINE_enum("command", None, ["csv", "python"], "What to generate.")
flags.DEFINE_string("map", "Acropolis", "Which map to use.")
flags.mark_flag_as_required("command")
FLAGS = flags.FLAGS
def get_data():
"""Retrieve static data from the game."""
run_config = run_configs.get()
with run_config.start(want_rgb=False) as controller:
m = maps.get(FLAGS.map)
create = sc_pb.RequestCreateGame(local_map=sc_pb.LocalMap(
map_path=m.path, map_data=m.data(run_config)))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer, race=sc_common.Random,
difficulty=sc_pb.VeryEasy)
join = sc_pb.RequestJoinGame(race=sc_common.Random,
options=sc_pb.InterfaceOptions(raw=True))
controller.create_game(create)
controller.join_game(join)
return controller.data()
def generate_name(ability):
return (ability.friendly_name or ability.button_name or
ability.link_name)
def sort_key(data, ability):
# Alphabetical, with specifics immediately after their generals.
name = generate_name(ability)
if ability.remaps_to_ability_id:
general = data.abilities[ability.remaps_to_ability_id]
name = "%s %s" % (generate_name(general), name)
return name
def generate_csv(data):
"""Generate a CSV of the abilities for easy commenting."""
print(",".join([
"ability_id",
"link_name",
"link_index",
"button_name",
"hotkey",
"friendly_name",
"remap_to",
"mismatch",
]))
for ability in sorted(data.abilities.values(),
key=lambda a: sort_key(data, a)):
ab_id = ability.ability_id
if ab_id in skip_abilities or (ab_id not in data.general_abilities and
ab_id not in used_abilities):
continue
general = ""
if ab_id in data.general_abilities:
general = "general"
elif ability.remaps_to_ability_id:
general = ability.remaps_to_ability_id
mismatch = ""
if ability.remaps_to_ability_id:
def check_mismatch(ability, parent, attr):
if getattr(ability, attr) != getattr(parent, attr):
return "%s: %s" % (attr, getattr(ability, attr))
parent = data.abilities[ability.remaps_to_ability_id]
mismatch = "; ".join(filter(None, [
check_mismatch(ability, parent, "available"),
check_mismatch(ability, parent, "target"),
check_mismatch(ability, parent, "allow_minimap"),
check_mismatch(ability, parent, "allow_autocast"),
check_mismatch(ability, parent, "is_building"),
check_mismatch(ability, parent, "footprint_radius"),
check_mismatch(ability, parent, "is_instant_placement"),
check_mismatch(ability, parent, "cast_range"),
]))
print(",".join(map(str, [
ability.ability_id,
ability.link_name,
ability.link_index,
ability.button_name,
ability.hotkey,
ability.friendly_name,
general,
mismatch,
])))
def generate_py_abilities(data):
"""Generate the list of functions in actions.py."""
def print_action(func_id, name, func, ab_id, general_id):
args = [func_id, '"%s"' % name, func, ab_id]
if general_id:
args.append(general_id)
print(" Function.ability(%s)," % ", ".join(str(v) for v in args))
func_ids = itertools.count(12) # Leave room for the ui funcs.
for ability in sorted(data.abilities.values(),
key=lambda a: sort_key(data, a)):
ab_id = ability.ability_id
if ab_id in skip_abilities or (ab_id not in data.general_abilities and
ab_id not in used_abilities):
continue
name = generate_name(ability).replace(" ", "_")
if ability.target in (sc_data.AbilityData.Target.Value("None"),
sc_data.AbilityData.PointOrNone):
print_action(next(func_ids), name + "_quick", "cmd_quick", ab_id,
ability.remaps_to_ability_id)
if ability.target != sc_data.AbilityData.Target.Value("None"):
print_action(next(func_ids), name+ "_screen", "cmd_screen", ab_id,
ability.remaps_to_ability_id)
if ability.allow_minimap:
print_action(next(func_ids), name + "_minimap", "cmd_minimap", ab_id,
ability.remaps_to_ability_id)
if ability.allow_autocast:
print_action(next(func_ids), name + "_autocast", "autocast", ab_id,
ability.remaps_to_ability_id)
def main(unused_argv):
data = get_data()
print("-" * 60)
if FLAGS.command == "csv":
generate_csv(data)
elif FLAGS.command == "python":
generate_py_abilities(data)
used_abilities = set(static_data.ABILITIES)
frivolous = {6, 7} # Dance and Cheer
# These need a slot id and so are exposed differently.
cancel_slot = {313, 1039, 305, 307, 309, 1832, 1834, 3672}
unload_unit = {410, 415, 397, 1440, 2373, 1409, 914, 3670}
skip_abilities = cancel_slot | unload_unit | frivolous
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/gen_data.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Generate the unit definitions for units.py."""
import collections
from absl import app
from pysc2 import maps
from pysc2 import run_configs
from pysc2.lib import static_data
from s2clientprotocol import common_pb2 as sc_common
from s2clientprotocol import sc2api_pb2 as sc_pb
def get_data():
"""Get the game's static data from an actual game."""
run_config = run_configs.get()
with run_config.start(want_rgb=False) as controller:
m = maps.get("Sequencer") # Arbitrary ladder map.
create = sc_pb.RequestCreateGame(local_map=sc_pb.LocalMap(
map_path=m.path, map_data=m.data(run_config)))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer, race=sc_common.Random,
difficulty=sc_pb.VeryEasy)
join = sc_pb.RequestJoinGame(race=sc_common.Random,
options=sc_pb.InterfaceOptions(raw=True))
controller.create_game(create)
controller.join_game(join)
return controller.data_raw()
def generate_py_units(data):
"""Generate the list of units in units.py."""
units = collections.defaultdict(list)
for unit in sorted(data.units, key=lambda a: a.name):
if unit.unit_id in static_data.UNIT_TYPES:
units[unit.race].append(unit)
def print_race(name, race):
print("class %s(enum.IntEnum):" % name)
print(' """%s units."""' % name)
for unit in units[race]:
print(" %s = %s" % (unit.name, unit.unit_id))
print("\n")
print(" units.py ".center(60, "-"))
print_race("Neutral", sc_common.NoRace)
print_race("Protoss", sc_common.Protoss)
print_race("Terran", sc_common.Terran)
print_race("Zerg", sc_common.Zerg)
def generate_py_buffs(data):
"""Generate the list of buffs in buffs.py."""
print(" buffs.py ".center(60, "-"))
print("class Buffs(enum.IntEnum):")
print(' """The list of buffs, as returned from RequestData."""')
for buff in sorted(data.buffs, key=lambda a: a.name):
if buff.name and buff.buff_id in static_data.BUFFS:
print(" %s = %s" % (buff.name, buff.buff_id))
print("\n")
def generate_py_upgrades(data):
"""Generate the list of upgrades in upgrades.py."""
print(" upgrades.py ".center(60, "-"))
print("class Upgrades(enum.IntEnum):")
print(' """The list of upgrades, as returned from RequestData."""')
for upgrade in sorted(data.upgrades, key=lambda a: a.name):
if upgrade.name and upgrade.upgrade_id in static_data.UPGRADES:
print(" %s = %s" % (upgrade.name, upgrade.upgrade_id))
print("\n")
def main(unused_argv):
data = get_data()
generate_py_units(data)
generate_py_buffs(data)
generate_py_upgrades(data)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/gen_versions.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Generate the list of versions for run_configs."""
from absl import app
import requests
# raw version of:
# https://github.com/Blizzard/s2client-proto/blob/master/buildinfo/versions.json
VERSIONS_FILE = "https://raw.githubusercontent.com/Blizzard/s2client-proto/master/buildinfo/versions.json"
def main(argv):
del argv # Unused.
versions = requests.get(VERSIONS_FILE).json()
for v in versions:
version_str = v["label"]
if version_str.count(".") == 1:
version_str += ".0"
print(' Version("%s", %i, "%s", None),' % (
version_str, v["base-version"], v["data-hash"]))
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/map_list.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Print the list of defined maps."""
from absl import app
from pysc2 import maps
def main(unused_argv):
for _, map_class in sorted(maps.get_maps().items()):
mp = map_class()
if mp.path:
print(mp, "\n")
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/mem_leak_check.py
================================================
#!/usr/bin/python
# Copyright 2018 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Test for memory leaks."""
# pylint: disable=g-import-not-at-top
import collections
import random
import sys
import time
from absl import app
from absl import flags
try:
import psutil
except ImportError:
sys.exit(
"`psutil` library required to track memory. This can be installed with:\n"
"$ pip install psutil\n"
"and needs the python-dev headers installed, for example:\n"
"$ apt install python-dev")
from pysc2 import maps
from pysc2 import run_configs
from pysc2.lib import protocol
from s2clientprotocol import common_pb2 as sc_common
from s2clientprotocol import sc2api_pb2 as sc_pb
# pylint: enable=g-import-not-at-top
races = {
"random": sc_common.Random,
"protoss": sc_common.Protoss,
"terran": sc_common.Terran,
"zerg": sc_common.Zerg,
}
flags.DEFINE_integer("mem_limit", 2000, "Max memory usage in Mb.")
flags.DEFINE_integer("episodes", 200, "Max number of episodes.")
flags.DEFINE_enum("race", "random", races.keys(), "Which race to play as.")
flags.DEFINE_list("map", "Catalyst", "Which map(s) to test on.")
FLAGS = flags.FLAGS
class Timestep(collections.namedtuple(
"Timestep", ["episode", "time", "cpu", "memory", "name"])):
def __str__(self):
return "[%3d: %7.3f] cpu: %5.1f s, mem: %4d Mb; %s" % self
def main(unused_argv):
for m in FLAGS.map: # Verify they're all valid.
maps.get(m)
interface = sc_pb.InterfaceOptions()
interface.raw = True
interface.score = True
interface.feature_layer.width = 24
interface.feature_layer.resolution.x = 84
interface.feature_layer.resolution.y = 84
interface.feature_layer.minimap_resolution.x = 64
interface.feature_layer.minimap_resolution.y = 64
timeline = []
start = time.time()
run_config = run_configs.get()
proc = run_config.start(want_rgb=interface.HasField("render"))
process = psutil.Process(proc.pid)
episode = 1
def add(s):
cpu_times = process.cpu_times() # pytype: disable=wrong-arg-count
cpu = cpu_times.user + cpu_times.system
mem = process.memory_info().rss / 2 ** 20 # In Mb # pytype: disable=wrong-arg-count
step = Timestep(episode, time.time() - start, cpu, mem, s)
print(step)
timeline.append(step)
if mem > FLAGS.mem_limit:
raise MemoryError("%s Mb mem limit exceeded" % FLAGS.mem_limit)
try:
add("Started process")
controller = proc.controller
for _ in range(FLAGS.episodes):
map_inst = maps.get(random.choice(FLAGS.map))
create = sc_pb.RequestCreateGame(
realtime=False, disable_fog=False, random_seed=episode,
local_map=sc_pb.LocalMap(map_path=map_inst.path,
map_data=map_inst.data(run_config)))
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer, race=races[FLAGS.race],
difficulty=sc_pb.CheatInsane)
join = sc_pb.RequestJoinGame(race=races[FLAGS.race], options=interface)
controller.create_game(create)
add("Created game on %s" % map_inst.name)
controller.join_game(join)
add("Joined game")
for i in range(2000):
controller.step(16)
obs = controller.observe()
if obs.player_result:
add("Lost on step %s" % i)
break
if i > 0 and i % 100 == 0:
add("Step %s" % i)
episode += 1
add("Done")
except KeyboardInterrupt:
pass
except (MemoryError, protocol.ConnectionError) as e:
print(e)
finally:
proc.close()
print("Timeline:")
for t in timeline:
print(t)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/play.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Run SC2 to play a game or a replay."""
import getpass
import platform
import sys
import time
from absl import app
from absl import flags
from pysc2 import maps
from pysc2 import run_configs
from pysc2.env import sc2_env
from pysc2.lib import point_flag
from pysc2.lib import renderer_human
from pysc2.lib import replay
from pysc2.lib import stopwatch
from s2clientprotocol import sc2api_pb2 as sc_pb
FLAGS = flags.FLAGS
flags.DEFINE_bool("render", True, "Whether to render with pygame.")
flags.DEFINE_bool("realtime", False, "Whether to run in realtime mode.")
flags.DEFINE_bool("full_screen", False, "Whether to run full screen.")
flags.DEFINE_float("fps", 22.4, "Frames per second to run the game.")
flags.DEFINE_integer("step_mul", 1, "Game steps per observation.")
flags.DEFINE_bool("render_sync", False, "Turn on sync rendering.")
point_flag.DEFINE_point("feature_screen_size", "84",
"Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
"Resolution for minimap feature layers.")
flags.DEFINE_integer("feature_camera_width", 24,
"Width of the feature layer camera.")
point_flag.DEFINE_point("rgb_screen_size", "256,192",
"Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", "128",
"Resolution for rendered minimap.")
point_flag.DEFINE_point("window_size", "640,480",
"Screen size if not full screen.")
flags.DEFINE_string("video", None, "Path to render a video of observations.")
flags.DEFINE_integer("max_game_steps", 0, "Total game steps to run.")
flags.DEFINE_integer("max_episode_steps", 0, "Total game steps per episode.")
flags.DEFINE_string("user_name", getpass.getuser(),
"Name of the human player for replays.")
flags.DEFINE_enum("user_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"User's race.")
flags.DEFINE_enum("bot_race", "random", sc2_env.Race._member_names_, # pylint: disable=protected-access
"AI race.")
flags.DEFINE_enum("difficulty", "very_easy", sc2_env.Difficulty._member_names_, # pylint: disable=protected-access
"Bot's strength.")
flags.DEFINE_enum("bot_build", "random", sc2_env.BotBuild._member_names_, # pylint: disable=protected-access
"Bot's build strategy.")
flags.DEFINE_bool("disable_fog", False, "Disable fog of war.")
flags.DEFINE_integer("observed_player", 1, "Which player to observe.")
flags.DEFINE_bool("profile", False, "Whether to turn on code profiling.")
flags.DEFINE_bool("trace", False, "Whether to trace the code execution.")
flags.DEFINE_bool("save_replay", True, "Whether to save a replay at the end.")
flags.DEFINE_string("map", None, "Name of a map to use to play.")
flags.DEFINE_bool("battle_net_map", False, "Use the battle.net map version.")
flags.DEFINE_string("map_path", None, "Override the map for this replay.")
flags.DEFINE_string("replay", None, "Name of a replay to show.")
def main(unused_argv):
"""Run SC2 to play a game or a replay."""
if FLAGS.trace:
stopwatch.sw.trace()
elif FLAGS.profile:
stopwatch.sw.enable()
if (FLAGS.map and FLAGS.replay) or (not FLAGS.map and not FLAGS.replay):
sys.exit("Must supply either a map or replay.")
if FLAGS.replay and not FLAGS.replay.lower().endswith("sc2replay"):
sys.exit("Replay must end in .SC2Replay.")
if FLAGS.realtime and FLAGS.replay:
# TODO(tewalds): Support realtime in replays once the game supports it.
sys.exit("realtime isn't possible for replays yet.")
if FLAGS.render and (FLAGS.realtime or FLAGS.full_screen):
sys.exit("disable pygame rendering if you want realtime or full_screen.")
if platform.system() == "Linux" and (FLAGS.realtime or FLAGS.full_screen):
sys.exit("realtime and full_screen only make sense on Windows/MacOS.")
if not FLAGS.render and FLAGS.render_sync:
sys.exit("render_sync only makes sense with pygame rendering on.")
run_config = run_configs.get()
interface = sc_pb.InterfaceOptions()
interface.raw = FLAGS.render
interface.raw_affects_selection = True
interface.raw_crop_to_playable_area = True
interface.score = True
interface.show_cloaked = True
interface.show_burrowed_shadows = True
interface.show_placeholders = True
if FLAGS.feature_screen_size and FLAGS.feature_minimap_size:
interface.feature_layer.width = FLAGS.feature_camera_width
FLAGS.feature_screen_size.assign_to(interface.feature_layer.resolution)
FLAGS.feature_minimap_size.assign_to(
interface.feature_layer.minimap_resolution)
interface.feature_layer.crop_to_playable_area = True
interface.feature_layer.allow_cheating_layers = True
if FLAGS.render and FLAGS.rgb_screen_size and FLAGS.rgb_minimap_size:
FLAGS.rgb_screen_size.assign_to(interface.render.resolution)
FLAGS.rgb_minimap_size.assign_to(interface.render.minimap_resolution)
max_episode_steps = FLAGS.max_episode_steps
if FLAGS.map:
create = sc_pb.RequestCreateGame(
realtime=FLAGS.realtime,
disable_fog=FLAGS.disable_fog)
try:
map_inst = maps.get(FLAGS.map)
except maps.lib.NoMapError:
if FLAGS.battle_net_map:
create.battlenet_map_name = FLAGS.map
else:
raise
else:
if map_inst.game_steps_per_episode:
max_episode_steps = map_inst.game_steps_per_episode
if FLAGS.battle_net_map:
create.battlenet_map_name = map_inst.battle_net
else:
create.local_map.map_path = map_inst.path
create.local_map.map_data = map_inst.data(run_config)
create.player_setup.add(type=sc_pb.Participant)
create.player_setup.add(type=sc_pb.Computer,
race=sc2_env.Race[FLAGS.bot_race],
difficulty=sc2_env.Difficulty[FLAGS.difficulty],
ai_build=sc2_env.BotBuild[FLAGS.bot_build])
join = sc_pb.RequestJoinGame(
options=interface, race=sc2_env.Race[FLAGS.user_race],
player_name=FLAGS.user_name)
version = None
else:
replay_data = run_config.replay_data(FLAGS.replay)
start_replay = sc_pb.RequestStartReplay(
replay_data=replay_data,
options=interface,
disable_fog=FLAGS.disable_fog,
observed_player_id=FLAGS.observed_player)
version = replay.get_replay_version(replay_data)
run_config = run_configs.get(version=version) # Replace the run config.
with run_config.start(
full_screen=FLAGS.full_screen,
window_size=FLAGS.window_size,
want_rgb=interface.HasField("render")) as controller:
if FLAGS.map:
controller.create_game(create)
controller.join_game(join)
else:
info = controller.replay_info(replay_data)
print(" Replay info ".center(60, "-"))
print(info)
print("-" * 60)
map_path = FLAGS.map_path or info.local_map_path
if map_path:
start_replay.map_data = run_config.map_data(map_path,
len(info.player_info))
controller.start_replay(start_replay)
if FLAGS.render:
renderer = renderer_human.RendererHuman(
fps=FLAGS.fps, step_mul=FLAGS.step_mul,
render_sync=FLAGS.render_sync, video=FLAGS.video)
renderer.run(
run_config, controller, max_game_steps=FLAGS.max_game_steps,
game_steps_per_episode=max_episode_steps,
save_replay=FLAGS.save_replay)
else: # Still step forward so the Mac/Windows renderer works.
try:
while True:
frame_start_time = time.time()
if not FLAGS.realtime:
controller.step(FLAGS.step_mul)
obs = controller.observe()
if obs.player_result:
break
time.sleep(max(0, frame_start_time + 1 / FLAGS.fps - time.time()))
except KeyboardInterrupt:
pass
print("Score: ", obs.observation.score.score)
print("Result: ", obs.player_result)
if FLAGS.map and FLAGS.save_replay:
replay_save_loc = run_config.save_replay(
controller.save_replay(), "local", FLAGS.map)
print("Replay saved to:", replay_save_loc)
# Save scores so we know how the human player did.
with open(replay_save_loc.replace("SC2Replay", "txt"), "w") as f:
f.write("{}\n".format(obs.observation.score.score))
if FLAGS.profile:
print(stopwatch.sw)
def entry_point(): # Needed so setup.py scripts work.
app.run(main)
if __name__ == "__main__":
app.run(main)
================================================
FILE: pysc2/bin/play_vs_agent.py
================================================
#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# 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
#
# http://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.
"""Play as a human against an agent by setting up a LAN game.
This needs to be called twice, once for the human, and once for the agent.
The human plays on the host. There you run it as:
$ python -m pysc2.bin.play_vs_agent --human --map