├── .coveragerc ├── .github └── workflows │ └── unit_test.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── banner.png ├── eat_others.gif ├── full.gif ├── merge.gif ├── mid_eject.gif ├── overview.gif ├── partial.gif ├── qr.png └── teamwork.gif ├── docs ├── en │ ├── Makefile │ ├── make.bat │ ├── requirements.txt │ └── source │ │ ├── _static │ │ └── css │ │ │ └── style.css │ │ ├── _templates │ │ └── layout.html │ │ ├── advanced │ │ ├── cfg_intro.rst │ │ ├── collision.rst │ │ └── images │ │ │ ├── 2f2s.png │ │ │ ├── 2f2s_v2.png │ │ │ ├── 2f2s_v3.png │ │ │ ├── changed_num_3000.png │ │ │ ├── eighth_merge.gif │ │ │ ├── iters_num_3000.png │ │ │ ├── quarter_merge.gif │ │ │ ├── query_num_3000.png │ │ │ └── straight_merge.gif │ │ ├── api_doc │ │ ├── agents.rst │ │ ├── balls.rst │ │ ├── index.rst │ │ ├── managers.rst │ │ ├── players.rst │ │ ├── render.rst │ │ ├── server.rst │ │ └── utils.rst │ │ ├── community │ │ └── faq.rst │ │ ├── conf.py │ │ ├── index.rst │ │ ├── installation │ │ └── index.rst │ │ └── tutorial │ │ ├── gobigger_engine.rst │ │ ├── gobigger_env.rst │ │ ├── images │ │ ├── eat_food.gif │ │ ├── eat_player.gif │ │ ├── eject.gif │ │ ├── eject_and_move.gif │ │ ├── eject_cross.gif │ │ ├── eject_merger.gif │ │ ├── eject_to_thorns.gif │ │ ├── fast_eat.gif │ │ ├── merge_quickly.gif │ │ ├── on_thorns.gif │ │ ├── overview.gif │ │ ├── split.gif │ │ ├── split_eat_all.gif │ │ └── split_merge.gif │ │ ├── index.rst │ │ ├── playback.rst │ │ ├── quick_start.rst │ │ ├── real_time_interaction_with_game.rst │ │ ├── space.rst │ │ └── what_is_gobigger.rst └── zh_CN │ ├── Makefile │ ├── make.bat │ ├── requirements.txt │ └── source │ ├── _static │ └── css │ │ └── style.css │ ├── _templates │ └── layout.html │ ├── advanced │ ├── cfg_intro.rst │ ├── collision.rst │ └── images │ │ ├── 2f2s.png │ │ ├── 2f2s_v2.png │ │ ├── 2f2s_v3.png │ │ ├── changed_num_3000.png │ │ ├── eighth_merge.gif │ │ ├── iters_num_3000.png │ │ ├── quarter_merge.gif │ │ ├── query_num_3000.png │ │ └── straight_merge.gif │ ├── community │ └── faq.rst │ ├── conf.py │ ├── index.rst │ ├── installation │ └── index.rst │ └── tutorial │ ├── gobigger_engine.rst │ ├── gobigger_env.rst │ ├── images │ ├── eat_food.gif │ ├── eat_player.gif │ ├── eject.gif │ ├── eject_and_move.gif │ ├── eject_cross.gif │ ├── eject_merger.gif │ ├── eject_to_thorns.gif │ ├── fast_eat.gif │ ├── merge_quickly.gif │ ├── on_thorns.gif │ ├── overview.gif │ ├── split.gif │ ├── split_eat_all.gif │ └── split_merge.gif │ ├── index.rst │ ├── playback.rst │ ├── quick_start.rst │ ├── real_time_interaction_with_game.rst │ ├── space.rst │ └── what_is_gobigger.rst ├── gobigger ├── __init__.py ├── agents │ ├── __init__.py │ ├── base_agent.py │ ├── bot_agent.py │ └── tests │ │ ├── test_base_agent.py │ │ └── test_bot_agent.py ├── balls │ ├── __init__.py │ ├── base_ball.py │ ├── clone_ball.py │ ├── food_ball.py │ ├── spore_ball.py │ ├── tests │ │ ├── test_base_ball.py │ │ ├── test_clone_ball.py │ │ ├── test_food_ball.py │ │ ├── test_spore_ball.py │ │ └── test_thorns_ball.py │ └── thorns_ball.py ├── bin │ ├── __init__.py │ ├── demo_bot.py │ ├── play.py │ ├── play_hyper.py │ ├── profile.py │ └── replayer.py ├── configs │ ├── __init__.py │ ├── server_default_config.py │ ├── server_sp_default_config.py │ ├── sp_t4p3.py │ ├── st_t1p1.py │ ├── st_t1p2.py │ ├── st_t2p1.py │ ├── st_t2p2.py │ ├── st_t3p2.py │ ├── st_t4p3.py │ ├── st_t5p3.py │ ├── st_t5p4.py │ └── st_t6p4.py ├── envs │ ├── __init__.py │ ├── gobigger_env.py │ ├── gobigger_sp_env.py │ └── tests │ │ ├── test_gobigger_env.py │ │ └── test_gobigger_sp_env.py ├── managers │ ├── __init__.py │ ├── base_manager.py │ ├── food_manager.py │ ├── player_manager.py │ ├── player_sp_manager.py │ ├── spore_manager.py │ ├── tests │ │ ├── test_base_manager.py │ │ ├── test_food_manager.py │ │ ├── test_player_manager.py │ │ ├── test_player_sp_manager.py │ │ ├── test_spore_manager.py │ │ └── test_thorns_manager.py │ └── thorns_manager.py ├── playbacks │ ├── __init__.py │ ├── action_pb.py │ ├── base_pb.py │ ├── frame_pb.py │ ├── null_pb.py │ ├── tests │ │ └── test_playback.py │ └── video_pb.py ├── players │ ├── __init__.py │ ├── base_player.py │ ├── human_player.py │ ├── human_sp_player.py │ └── tests │ │ ├── test_base_player.py │ │ ├── test_human_player.py │ │ └── test_human_sp_player.py ├── render │ ├── __init__.py │ ├── base_render.py │ ├── env_render.py │ ├── pb_render.py │ ├── realtime_render.py │ └── tests │ │ ├── test_env_render.py │ │ └── test_realtime_render.py ├── server │ ├── __init__.py │ ├── server.py │ ├── server_sp.py │ └── tests │ │ ├── test_server.py │ │ └── test_server_sp.py └── utils │ ├── __init__.py │ ├── collision_detection.py │ ├── colors.py │ ├── config_utils.py │ ├── frame_utils.py │ ├── generator_utils.py │ ├── obs_utils.py │ ├── structures.py │ ├── test │ ├── test_collision_detection.py │ ├── test_config_utils.py │ ├── test_sequence_generator.py │ └── test_structures.py │ └── tool.py ├── practice ├── README.md ├── battle.py ├── cooperative_agent │ ├── agent.py │ ├── default_model_config.yaml │ └── model.py ├── solo_agent │ ├── agent.py │ ├── default_model_config.yaml │ ├── model.py │ └── util.py ├── test_ai.py └── tools │ ├── encoder.py │ ├── features.py │ ├── head.py │ ├── network │ ├── __init__.py │ ├── activation.py │ ├── encoder.py │ ├── nn_module.py │ ├── normalization.py │ ├── res_block.py │ ├── rnn.py │ ├── scatter_connection.py │ ├── soft_argmax.py │ └── transformer.py │ └── util.py └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = gobigger/bin/* -------------------------------------------------------------------------------- /.github/workflows/unit_test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will check pytest 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: unit_test 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | test_unittest: 10 | runs-on: ubuntu-latest 11 | if: "!contains(github.event.head_commit.message, 'ci skip')" 12 | strategy: 13 | matrix: 14 | python-version: [3.7] 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install depedendencies 24 | run: | 25 | pip install pytest-cov 26 | pip install -e . 27 | - name: Generate coverage report 28 | run: | 29 | pytest ./gobigger --cov=./ --cov-report=xml --cov-report=term-missing --durations=0 -v 30 | - name: Upload coverage to Codecov 31 | uses: codecov/codecov-action@v1 32 | with: 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | file: ./coverage.xml 35 | flags: unittests 36 | name: codecov-umbrella 37 | fail_ci_if_error: false 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | *.mp4 131 | .idea/ 132 | 133 | *.DS_Store 134 | *.pb 135 | *.ac 136 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/banner.png -------------------------------------------------------------------------------- /assets/eat_others.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/eat_others.gif -------------------------------------------------------------------------------- /assets/full.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/full.gif -------------------------------------------------------------------------------- /assets/merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/merge.gif -------------------------------------------------------------------------------- /assets/mid_eject.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/mid_eject.gif -------------------------------------------------------------------------------- /assets/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/overview.gif -------------------------------------------------------------------------------- /assets/partial.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/partial.gif -------------------------------------------------------------------------------- /assets/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/qr.png -------------------------------------------------------------------------------- /assets/teamwork.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/assets/teamwork.gif -------------------------------------------------------------------------------- /docs/en/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/en/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/en/requirements.txt: -------------------------------------------------------------------------------- 1 | easydict 2 | gym 3 | pygame 4 | pytest 5 | opencv-python 6 | numpy 7 | sphinx 8 | tqdm 9 | trueskill 10 | git+http://github.com/opendilab/GoBigger@main 11 | -------------------------------------------------------------------------------- /docs/en/source/_static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 3 | } 4 | 5 | /* Default header fonts are ugly */ 6 | h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend, p.caption { 7 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 8 | } 9 | 10 | /* Use white for docs background */ 11 | .wy-side-nav-search { 12 | background-color: #fff; 13 | } 14 | 15 | .wy-nav-content { 16 | max-width: 1200px !important; 17 | } 18 | 19 | .wy-nav-content-wrap, .wy-menu li.current > a { 20 | background-color: #fff; 21 | } 22 | 23 | .wy-side-nav-search>a img.logo { 24 | width: 80%; 25 | margin-top: 10px; 26 | } 27 | 28 | @media screen and (min-width: 1400px) { 29 | .wy-nav-content-wrap { 30 | background-color: #fff; 31 | } 32 | 33 | .wy-nav-content { 34 | background-color: #fff; 35 | } 36 | } 37 | 38 | /* Fixes for mobile */ 39 | .wy-nav-top { 40 | background-color: #fff; 41 | /*background-image: url('../images/tianshou-logo.png');*/ 42 | background-repeat: no-repeat; 43 | background-position: center; 44 | padding: 0; 45 | margin: 0.4045em 0.809em; 46 | color: #333; 47 | } 48 | 49 | .wy-nav-top > a { 50 | display: none; 51 | } 52 | 53 | @media screen and (max-width: 768px) { 54 | .wy-side-nav-search>a img.logo { 55 | height: 60px; 56 | } 57 | } 58 | 59 | /* This is needed to ensure that logo above search scales properly */ 60 | .wy-side-nav-search a { 61 | display: block; 62 | } 63 | 64 | /* This ensures that multiple constructors will remain in separate lines. */ 65 | .rst-content dl:not(.docutils) dt { 66 | display: table; 67 | } 68 | 69 | /* Use our red for literals (it's very similar to the original color) */ 70 | .rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { 71 | color: #4692BC; 72 | } 73 | 74 | .rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, 75 | .rst-content code.xref, a .rst-content tt, a .rst-content code { 76 | color: #404040; 77 | } 78 | 79 | /* Change link colors (except for the menu) */ 80 | 81 | a { 82 | color: #4692BC; 83 | } 84 | 85 | a:hover { 86 | color: #4692BC; 87 | } 88 | 89 | 90 | a:visited { 91 | color: #4692BC; 92 | } 93 | 94 | .wy-menu a { 95 | color: #b3b3b3; 96 | } 97 | 98 | .wy-menu a:hover { 99 | color: #b3b3b3; 100 | } 101 | 102 | /* Default footer text is quite big */ 103 | footer { 104 | font-size: 80%; 105 | } 106 | 107 | footer .rst-footer-buttons { 108 | font-size: 125%; /* revert footer settings - 1/80% = 125% */ 109 | } 110 | 111 | footer p { 112 | font-size: 100%; 113 | } 114 | 115 | .ethical-rtd { 116 | display: none; 117 | } 118 | 119 | .ethical-fixedfooter { 120 | display: none; 121 | } 122 | 123 | .ethical-content { 124 | display: none; 125 | } 126 | 127 | /* For hidden headers that appear in TOC tree */ 128 | /* see http://stackoverflow.com/a/32363545/3343043 */ 129 | .rst-content .hidden-section { 130 | display: none; 131 | } 132 | 133 | nav .hidden-section { 134 | display: inherit; 135 | } 136 | 137 | .wy-side-nav-search>div.version { 138 | color: #000; 139 | } -------------------------------------------------------------------------------- /docs/en/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} -------------------------------------------------------------------------------- /docs/en/source/advanced/cfg_intro.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ########################################## 3 | 4 | 5 | Overview 6 | ====================== 7 | 8 | In order to allow players to further understand the realization of the game mechanism, we have opened some parameters for players to adjust. We hope that players can modify the corresponding parameters to achieve a variety of different environments. At the same time, you can also design your own agent environment to quickly verify the algorithm. 9 | 10 | GoBigger puts configurable parameters in ``gobigger/server/server_default_config.py`` 11 | -------------------------------------------------------------------------------- /docs/en/source/advanced/images/2f2s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/2f2s.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/2f2s_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/2f2s_v2.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/2f2s_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/2f2s_v3.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/changed_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/changed_num_3000.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/eighth_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/eighth_merge.gif -------------------------------------------------------------------------------- /docs/en/source/advanced/images/iters_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/iters_num_3000.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/quarter_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/quarter_merge.gif -------------------------------------------------------------------------------- /docs/en/source/advanced/images/query_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/query_num_3000.png -------------------------------------------------------------------------------- /docs/en/source/advanced/images/straight_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/advanced/images/straight_merge.gif -------------------------------------------------------------------------------- /docs/en/source/api_doc/agents.rst: -------------------------------------------------------------------------------- 1 | agents 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | 8 | BaseAgent 9 | ======================= 10 | .. autoclass:: gobigger.agents.base_agent.BaseAgent 11 | :members: 12 | 13 | 14 | BotAgent 15 | ======================= 16 | .. autoclass:: gobigger.agents.bot_agent.BotAgent 17 | :members: 18 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/balls.rst: -------------------------------------------------------------------------------- 1 | balls 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | 8 | BaseBall 9 | ======================= 10 | .. autoclass:: gobigger.balls.base_ball.BaseBall 11 | :members: 12 | 13 | 14 | FoodBall 15 | ======================= 16 | .. autoclass:: gobigger.balls.food_ball.FoodBall 17 | :members: 18 | 19 | 20 | ThornsBall 21 | ======================= 22 | .. autoclass:: gobigger.balls.thorns_ball.ThornsBall 23 | :members: 24 | 25 | 26 | CloneBall 27 | ======================= 28 | .. autoclass:: gobigger.balls.clone_ball.CloneBall 29 | :members: 30 | 31 | 32 | SporeBall 33 | ======================= 34 | .. autoclass:: gobigger.balls.spore_ball.SporeBall 35 | :members: 36 | 37 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/index.rst: -------------------------------------------------------------------------------- 1 | .. pod documentation master file, created by 2 | sphinx-quickstart on Jan 25 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | API Doc 7 | ########## 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | 12 | agents 13 | balls 14 | managers 15 | players 16 | render 17 | server 18 | utils 19 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/managers.rst: -------------------------------------------------------------------------------- 1 | managers 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | 8 | BaseManager 9 | ======================= 10 | .. autoclass:: gobigger.managers.base_manager.BaseManager 11 | :members: 12 | 13 | 14 | FoodManager 15 | ======================= 16 | .. autoclass:: gobigger.managers.food_manager.FoodManager 17 | :members: 18 | 19 | 20 | ThornsManager 21 | ======================= 22 | .. autoclass:: gobigger.managers.thorns_manager.ThornsManager 23 | :members: 24 | 25 | 26 | PlayerManager 27 | ======================= 28 | .. autoclass:: gobigger.managers.player_manager.PlayerManager 29 | :members: 30 | 31 | 32 | SporeManager 33 | ======================= 34 | .. autoclass:: gobigger.managers.spore_manager.SporeManager 35 | :members: 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/players.rst: -------------------------------------------------------------------------------- 1 | players 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | 8 | BasePlayer 9 | ======================= 10 | .. autoclass:: gobigger.players.base_player.BasePlayer 11 | :members: 12 | 13 | 14 | HumanPlayer 15 | ======================= 16 | .. autoclass:: gobigger.players.human_player.HumanPlayer 17 | :members: 18 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/render.rst: -------------------------------------------------------------------------------- 1 | render 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | 8 | BaseRender 9 | ======================= 10 | .. autoclass:: gobigger.render.base_render.BaseRender 11 | :members: 12 | 13 | 14 | EnvRender 15 | ======================= 16 | .. autoclass:: gobigger.render.env_render.EnvRender 17 | :members: 18 | 19 | 20 | RealtimeRender 21 | ======================= 22 | .. autoclass:: gobigger.render.realtime_render.RealtimeRender 23 | :members: 24 | 25 | 26 | RealtimePartialRender 27 | ======================= 28 | .. autoclass:: gobigger.render.realtime_render.RealtimePartialRender 29 | :members: 30 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/server.rst: -------------------------------------------------------------------------------- 1 | server 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | 8 | Server 9 | ======================= 10 | .. autoclass:: gobigger.server.server.Server 11 | :members: 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/en/source/api_doc/utils.rst: -------------------------------------------------------------------------------- 1 | utils 2 | ################## 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | 8 | Border 9 | ======================= 10 | .. autoclass:: gobigger.utils.structures.Border 11 | :members: 12 | 13 | 14 | BaseCollisionDetection 15 | ========================================= 16 | .. autoclass:: gobigger.utils.collision_detection.BaseCollisionDetection 17 | :members: 18 | 19 | ExhaustiveCollisionDetection 20 | ========================================= 21 | .. autoclass:: gobigger.utils.collision_detection.ExhaustiveCollisionDetection 22 | :members: 23 | 24 | PrecisionCollisionDetection 25 | ========================================= 26 | .. autoclass:: gobigger.utils.collision_detection.PrecisionCollisionDetection 27 | :members: 28 | 29 | RebuildQuadTreeCollisionDetection 30 | ========================================= 31 | .. autoclass:: gobigger.utils.collision_detection.RebuildQuadTreeCollisionDetection 32 | :members: 33 | 34 | RemoveQuadTreeCollisionDetection 35 | ========================================= 36 | .. autoclass:: gobigger.utils.collision_detection.RemoveQuadTreeCollisionDetection 37 | :members: -------------------------------------------------------------------------------- /docs/en/source/community/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | ############## 3 | 4 | 5 | Q1: How to save video in a game? 6 | *********************************** 7 | 8 | :A1: 9 | 10 | Set ``playback_settings`` when you create the environment. After finishing a game, ``test.pb`` will be saved at ``save_dir``, which can be shown by the GoBigger replayer. 11 | 12 | .. code-block:: 13 | 14 | env = create_env('st_t3p2', dict( 15 | playback_settings=dict( 16 | playback_type='by_frame', 17 | by_frame=dict( 18 | save_frame=True, 19 | save_dir='.', 20 | save_name_prefix='test', 21 | ), 22 | ), 23 | )) 24 | 25 | 26 | 27 | Q2: What are the winning conditions at the end of the game? 28 | ********************************************************************** 29 | 30 | :A2: 31 | 32 | Sort by calculating the sum of the scores of all players under each team at the end of the game. 33 | 34 | 35 | Q3: Is there a limit to the size of the partial field of view? 36 | ********************************************************************** 37 | 38 | :A3: 39 | 40 | The size of the player's partial field of view is determined by the relative position of the player's doppelganger. We set the player’s minimum field of view to be a matrix of 36*36. With the dispersion of the doppelganger, the player's maximum field of vision can reach the global level. 41 | -------------------------------------------------------------------------------- /docs/en/source/index.rst: -------------------------------------------------------------------------------- 1 | .. gobigger documentation master file, created by 2 | sphinx-quickstart on Fri Aug 27 11:46:57 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to GoBigger's documentation! 7 | ========================================= 8 | 9 | Overview 10 | ------------ 11 | GoBigger is a **multi-agent** environment for reinforce learning. It is similar to `Agar `_, which is a massively multiplayer online action game created by Brazilian developer Matheus Valadares. In GoBigger, players control one or more circular balls in a map. The goal is to gain as much size as possible by eating food balls and other balls smaller than the player's balls while avoiding larger ones which can eat the player's balls. Each player starts with one ball, but players can split a ball into two once it reaches a sufficient size, allowing them to control multiple balls. 12 | 13 | GoBigger allows users to interact with the multi-agent environment easily. Through the given interface, users can get observations by actions created by their policy. Users can also customize their game environments by changing the args in the config. 14 | 15 | Indices and tables 16 | ===================== 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | :caption: Tutorial 21 | 22 | installation/index 23 | tutorial/quick_start 24 | tutorial/what_is_gobigger 25 | tutorial/gobigger_engine 26 | tutorial/real_time_interaction_with_game 27 | tutorial/space 28 | tutorial/gobigger_env 29 | tutorial/playback 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | :caption: Advanced 34 | 35 | advanced/cfg_intro 36 | advanced/collision 37 | 38 | .. toctree:: 39 | :maxdepth: 2 40 | :caption: Commutity 41 | 42 | community/faq 43 | 44 | .. toctree:: 45 | :maxdepth: 2 46 | :caption: API Docs 47 | 48 | api_doc/agents 49 | api_doc/balls 50 | api_doc/managers 51 | api_doc/players 52 | api_doc/render 53 | api_doc/server 54 | api_doc/utils 55 | -------------------------------------------------------------------------------- /docs/en/source/installation/index.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ############## 3 | 4 | Prerequisites 5 | ================= 6 | 7 | System version: 8 | 9 | * Centos 7 10 | * Windows 10 11 | * MacOS 12 | 13 | Python version: 3.6.8 14 | 15 | Get and install GoBigger 16 | ============================= 17 | 18 | You can simply install GoBigger from PyPI with the following command: 19 | 20 | .. code-block:: bash 21 | 22 | pip install gobigger 23 | 24 | 25 | If you use Anaconda or Miniconda, you can install GoBigger through the following command: 26 | 27 | .. code-block:: bash 28 | 29 | conda install -c opendilab gobigger 30 | 31 | 32 | You can also install with newest version through GitHub. First get and download the official repository with the following command line. 33 | 34 | .. code-block:: bash 35 | 36 | git clone https://github.com/opendilab/GoBigger.git 37 | 38 | Then you can install from source: 39 | 40 | .. code-block:: bash 41 | 42 | # install for use 43 | # Note: use `--user` option to install the related packages in the user own directory(e.g.: ~/.local) 44 | pip install . --user 45 | 46 | # install for development(if you want to modify GoBigger) 47 | pip install -e . --user 48 | 49 | 50 | -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eat_food.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eat_food.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eat_player.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eat_player.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eject.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eject.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eject_and_move.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eject_and_move.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eject_cross.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eject_cross.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eject_merger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eject_merger.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/eject_to_thorns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/eject_to_thorns.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/fast_eat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/fast_eat.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/merge_quickly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/merge_quickly.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/on_thorns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/on_thorns.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/overview.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/split.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/split_eat_all.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/split_eat_all.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/images/split_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/en/source/tutorial/images/split_merge.gif -------------------------------------------------------------------------------- /docs/en/source/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ########## 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | quick_start 8 | what_is_gobigger 9 | gobigger_engine 10 | real_time_interaction_with_game 11 | space 12 | gobigger_env 13 | playback 14 | -------------------------------------------------------------------------------- /docs/en/source/tutorial/playback.rst: -------------------------------------------------------------------------------- 1 | Playback System 2 | ################## 3 | 4 | GoBigger's playback system supports three options, which can be selected through the environment's configuration file. The configuration items involved are as follows: 5 | 6 | .. code-block:: python 7 | 8 | config = dict( 9 | ... 10 | playback_settings=dict( 11 | playback_type='none', # ['none', 'by_video', 'by_frame'] 12 | by_video=dict( 13 | save_video=False, 14 | save_fps=10, 15 | save_resolution=552, 16 | save_all=True, 17 | save_partial=False, 18 | save_dir='.', 19 | save_name_prefix='test', 20 | ), 21 | by_frame=dict( 22 | save_frame=False, 23 | save_all=True, 24 | save_partial=False, 25 | save_dir='.', 26 | save_name_prefix='test', 27 | ), 28 | by_action=dict( 29 | save_action=False, 30 | save_dir='.', 31 | save_name_prefix='test', 32 | ), 33 | ), 34 | ... 35 | ) 36 | 37 | ``playback_type`` can be one of ``['none', 'by_video', 'by_frame']``. 38 | 39 | * ``none``: means no need to save playback 40 | * ``by_video``: means to save the video directly, and the suffix of the saved file is ``.mp4``. Generally speaking, the video saved in the ``st_t4p3`` environment is about 80M. 41 | * ``by_frame``: Represents the change amount of each frame saved, and the suffix of the saved file is ``.pb``. Generally speaking, the ``st_t4p3`` environment saves files around 25M. 42 | 43 | 44 | Save video 45 | ---------------------------- 46 | 47 | If ``playback_type='by_video'`` is selected, the specific configuration items can be as follows: 48 | 49 | .. code-block:: python 50 | 51 | env = create_env('st_t4p3', dict( 52 | playback_settings=dict( 53 | playback_type='by_video', 54 | by_video=dict( 55 | save_video=True, 56 | save_dir='.', # The directory location where the video needs to be saved 57 | save_name_prefix='test', # Save the prefix of the video name 58 | ), 59 | ), 60 | )) 61 | 62 | 63 | Save the pb file 64 | ---------------------------- 65 | 66 | If ``playback_type='by_frame'`` is selected, the specific configuration items can be as follows: 67 | 68 | .. code-block:: python 69 | 70 | env = create_env('st_t4p3', dict( 71 | playback_settings=dict( 72 | playback_type='by_frame', 73 | by_frame=dict( 74 | save_frame=True, 75 | save_dir='.', # The directory location where the video needs to be saved 76 | save_name_prefix='test', # The prefix of the name of the saving video 77 | ) 78 | ), 79 | )) 80 | 81 | After getting the saved ``.pb`` file, you need to view it through our given player. Execute the following command in the command line to open the player. 82 | 83 | .. code-block:: python 84 | 85 | python -m gobigger.bin.replayer 86 | 87 | After opening the player, you need to select the ``.pb`` file you want to view. Then you can start watching. The player supports double-speed playback, including 2x, 4x, and 8x (by clicking the button in the lower left corner). Also supports dragging the progress bar. 88 | -------------------------------------------------------------------------------- /docs/en/source/tutorial/quick_start.rst: -------------------------------------------------------------------------------- 1 | Quick Start 2 | ############## 3 | 4 | Launch a game environment 5 | ================================== 6 | 7 | After installation, you can launch your game environment easily according the following code: 8 | 9 | .. code-block:: python 10 | 11 | import random 12 | from gobigger.envs import create_env 13 | 14 | env = create_env('st_t2p2') 15 | obs = env.reset() 16 | for i in range(1000): 17 | actions = {0: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 18 | 1: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 19 | 2: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 20 | 3: [random.uniform(-1, 1), random.uniform(-1, 1), 0]} 21 | obs, rew, done, info = env.step(actions) 22 | print('[{}] leaderboard={}'.format(i, obs[0]['leaderboard'])) 23 | if done: 24 | print('finish game!') 25 | break 26 | env.close() 27 | 28 | You will see output as following. It shows the frame number and the leaderboard per frame. 29 | 30 | .. code-block:: 31 | 32 | [0] leaderboard={0: 3000, 1: 3100.0} 33 | [1] leaderboard={0: 3000, 1: 3100.0} 34 | [2] leaderboard={0: 3000, 1: 3100.0} 35 | [3] leaderboard={0: 3000, 1: 3100.0} 36 | [4] leaderboard={0: 3000, 1: 3100.0} 37 | [5] leaderboard={0: 3000, 1: 3100.0} 38 | [6] leaderboard={0: 3000, 1: 3100.0} 39 | [7] leaderboard={0: 3000, 1: 3100.0} 40 | [8] leaderboard={0: 3000, 1: 3100.0} 41 | [9] leaderboard={0: 3000, 1: 3100.0} 42 | [10] leaderboard={0: 3000, 1: 3100.0} 43 | ... 44 | 45 | 46 | Customize your config 47 | ============================ 48 | 49 | Users can also choose to customize the game environment by modifying the configuration cfg and through the ``gobigger.envs.create_env_custom`` method we provide. The ``gobigger.envs.create_env_custom`` method accepts two parameters, the first parameter is ``type``, the optional value is ``st`` or ``sp``, which represent the standard game mode, and the independent action game mode. See the introduction of the two modes for details. Below we give a few simple examples based on the standard game mode. 50 | 51 | Add more players in a game 52 | ------------------------------------ 53 | 54 | For example, you may want to allow 6 teams and 2 players per team in your game, and then please modify ``team_num`` and ``player_num_per_team`` in config. 55 | 56 | .. code-block:: python 57 | 58 | from gobigger.envs import create_env_custom 59 | 60 | env = create_env_custom(type='st', cfg=dict( 61 | team_num=6, 62 | player_num_per_team=2 63 | )) 64 | 65 | Extend the game time 66 | ------------------------------------ 67 | If you want to extend the game time to 20 minutes (24000 frames), you can use the following codes. 68 | 69 | .. code-block:: python 70 | 71 | from gobigger.envs import create_env_custom 72 | 73 | env = create_env_custom(type='st', cfg=dict( 74 | frame_limit=24000 75 | )) 76 | 77 | Change the size of the map 78 | ------------------------------------ 79 | 80 | If you want to have a larger map, you can change ``map_width`` and ``map_height`` in config. 81 | 82 | .. code-block:: python 83 | 84 | from gobigger.envs import create_env_custom 85 | 86 | env = create_env_custom(type='st', cfg=dict( 87 | map_width=1000, 88 | map_height=1000, 89 | )) 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/en/source/tutorial/real_time_interaction_with_game.rst: -------------------------------------------------------------------------------- 1 | Real-time Interaction with game 2 | ########################################## 3 | 4 | GoBigger allow users to play game on their personal computer in real-time. Serveral modes are supported for users to explore this game. 5 | 6 | .. note:: 7 | 8 | If your version of GoBigger is v0.1.x, please upgrade for a better experience. You can use ``pip install --upgrade gobigger`` to access the latest version of GoBigger. 9 | 10 | 11 | Standard setting with partial view 12 | -------------------------------------- 13 | 14 | If you want to play real-time game on your PC on your own, you can launch a game with the following code: 15 | 16 | .. code-block:: bash 17 | 18 | python -m gobigger.bin.play --mode st --vision-type partial 19 | 20 | In this mode, using mouse to control the your balls to move, ``Q`` means eject spore on your moving direction, ``W`` means split your balls, and ``E`` means stop all your balls and gather them together. 21 | 22 | 23 | Standard setting with full view 24 | -------------------------------------- 25 | 26 | If you want to play real-time game on your PC with your friends, you can launch a game with the following code: 27 | 28 | .. code-block:: bash 29 | 30 | python -m gobigger.bin.play --mode st --vision-type full 31 | 32 | 33 | Independent action setting with partial view 34 | ------------------------------------------------ 35 | 36 | In the independent action game mode, each ball of the player receives an action individually. But because it is more difficult to control, we still only receive an action based on the mouse and keyboard in this mode, and assign this action to all the player's balls. The game can be started with the following code: 37 | 38 | .. code-block:: bash 39 | 40 | python -m gobigger.bin.play --mode sp --vision-type partial 41 | 42 | -------------------------------------------------------------------------------- /docs/zh_CN/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/zh_CN/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/zh_CN/requirements.txt: -------------------------------------------------------------------------------- 1 | easydict 2 | gym 3 | pygame 4 | pytest 5 | opencv-python 6 | numpy 7 | sphinx 8 | tqdm 9 | trueskill 10 | git+http://github.com/opendilab/GoBigger@main 11 | -------------------------------------------------------------------------------- /docs/zh_CN/source/_static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 3 | } 4 | 5 | /* Default header fonts are ugly */ 6 | h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend, p.caption { 7 | font-family: "Lato","proxima-nova","Helvetica Neue",Arial,sans-serif; 8 | } 9 | 10 | /* Use white for docs background */ 11 | .wy-side-nav-search { 12 | background-color: #fff; 13 | } 14 | 15 | .wy-nav-content { 16 | max-width: 1200px !important; 17 | } 18 | 19 | .wy-nav-content-wrap, .wy-menu li.current > a { 20 | background-color: #fff; 21 | } 22 | 23 | .wy-side-nav-search>a img.logo { 24 | width: 80%; 25 | margin-top: 10px; 26 | } 27 | 28 | @media screen and (min-width: 1400px) { 29 | .wy-nav-content-wrap { 30 | background-color: #fff; 31 | } 32 | 33 | .wy-nav-content { 34 | background-color: #fff; 35 | } 36 | } 37 | 38 | /* Fixes for mobile */ 39 | .wy-nav-top { 40 | background-color: #fff; 41 | /*background-image: url('../images/tianshou-logo.png');*/ 42 | background-repeat: no-repeat; 43 | background-position: center; 44 | padding: 0; 45 | margin: 0.4045em 0.809em; 46 | color: #333; 47 | } 48 | 49 | .wy-nav-top > a { 50 | display: none; 51 | } 52 | 53 | @media screen and (max-width: 768px) { 54 | .wy-side-nav-search>a img.logo { 55 | height: 60px; 56 | } 57 | } 58 | 59 | /* This is needed to ensure that logo above search scales properly */ 60 | .wy-side-nav-search a { 61 | display: block; 62 | } 63 | 64 | /* This ensures that multiple constructors will remain in separate lines. */ 65 | .rst-content dl:not(.docutils) dt { 66 | display: table; 67 | } 68 | 69 | /* Use our red for literals (it's very similar to the original color) */ 70 | .rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal { 71 | color: #4692BC; 72 | } 73 | 74 | .rst-content tt.xref, a .rst-content tt, .rst-content tt.xref, 75 | .rst-content code.xref, a .rst-content tt, a .rst-content code { 76 | color: #404040; 77 | } 78 | 79 | /* Change link colors (except for the menu) */ 80 | 81 | a { 82 | color: #4692BC; 83 | } 84 | 85 | a:hover { 86 | color: #4692BC; 87 | } 88 | 89 | 90 | a:visited { 91 | color: #4692BC; 92 | } 93 | 94 | .wy-menu a { 95 | color: #b3b3b3; 96 | } 97 | 98 | .wy-menu a:hover { 99 | color: #b3b3b3; 100 | } 101 | 102 | /* Default footer text is quite big */ 103 | footer { 104 | font-size: 80%; 105 | } 106 | 107 | footer .rst-footer-buttons { 108 | font-size: 125%; /* revert footer settings - 1/80% = 125% */ 109 | } 110 | 111 | footer p { 112 | font-size: 100%; 113 | } 114 | 115 | .ethical-rtd { 116 | display: none; 117 | } 118 | 119 | .ethical-fixedfooter { 120 | display: none; 121 | } 122 | 123 | .ethical-content { 124 | display: none; 125 | } 126 | 127 | /* For hidden headers that appear in TOC tree */ 128 | /* see http://stackoverflow.com/a/32363545/3343043 */ 129 | .rst-content .hidden-section { 130 | display: none; 131 | } 132 | 133 | nav .hidden-section { 134 | display: inherit; 135 | } 136 | 137 | .wy-side-nav-search>div.version { 138 | color: #000; 139 | } -------------------------------------------------------------------------------- /docs/zh_CN/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/cfg_intro.rst: -------------------------------------------------------------------------------- 1 | 游戏配置介绍 2 | ############## 3 | 4 | 5 | 总览 6 | ====================== 7 | 8 | 为了让玩家更进一步了解游戏机制的实现,我们开放了部分参数供玩家进行调整。我们希望玩家可以通过修改对应的参数,来实现各种不同的环境。同时,也可以设计自己的代理环境,用于对算法的快速验证。 9 | 10 | GoBigger 将可配置参数统一放在 ``gobigger/server/server_default_config.py``。其中对可配置参数进行详细介绍。 11 | 12 | -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/collision.rst: -------------------------------------------------------------------------------- 1 | 碰撞检测算法 2 | ############## 3 | 4 | 总览 5 | ====================== 6 | 7 | 为了在游戏的每一帧中检测球体的碰撞,以便更新球体的状态,我们需要设计高效的碰撞检测算法。因此,我们设计了四种碰撞检测算法,并将它们封装成以下四类。下面仅介绍算法相关结果,更多细节请查看源码。 8 | 9 | 算法介绍 10 | ====================== 11 | 12 | 四种算法的介绍和理论时间复杂度如下: 13 | 14 | **ExhaustiveCollisionDetection:** 15 | 16 | :math:`O(n*m)` 17 | 18 | 暴力解法,其中 n 表示球的总数,m 表示要询问的球数。 19 | 20 | **PrecisionCollisionDetection:** 21 | 22 | :math:`O(n*log(n)+\Sigma{r}*logn+p)` 23 | 24 | 卡精度解法。其中 n 表示球的总数,m 表示要询问的球数,k 表示我们设置的精度,p 表示实际碰撞的球体数量。 25 | 26 | **RebuildQuadTreeCollisionDetection:** 27 | 28 | :math:`O(n*log(n) + m*log(n)+p)` 29 | 30 | 重建四叉树解法。其中 n 表示球的总数,m 表示要询问的球数,p 表示实际碰撞的球体数量。 31 | 32 | **RemoveQuadTreeCollisionDetection:** 33 | 34 | :math:`O(r*log(n)+m*log(n)+p)` 35 | 36 | 优化后的重建四叉树解法,增加了对四叉树的删除和维护。其中n表示球的总数,m表示要询问的球数,r表示位置状态发生变化的球体数量,p表示实际碰撞的球体数量。 37 | 38 | 为了测试这些算法的效率,我们修改了球总数、查询数、改变球数、迭代轮数等参数来设置测试场景。下表中的数据来自最具代表性的场景: 39 | 40 | * `T:地图中所有球的数量` 41 | * `Q:查询球的数量,通常表示地图中移动的球` 42 | * `C:需要修改的球的数量,即碰撞次数` 43 | 44 | +----------+------------+-----------+------------------+-----------------+ 45 | | | Exhaustive | Precision | Rebuild QuadTree | Remove QuadTree | 46 | +==========+============+===========+==================+=================+ 47 | | T=3000 | 688ms | 14ms | 47ms | 48ms | 48 | | | | | | | 49 | | Q=300 | | | | | 50 | | | | | | | 51 | | C=600 | | | | | 52 | +----------+------------+-----------+------------------+-----------------+ 53 | | T=3000 | 1067ms | 16ms | 50ms | 178ms | 54 | | | | | | | 55 | | Q=300 | | | | | 56 | | | | | | | 57 | | C=1500 | | | | | 58 | +----------+------------+-----------+------------------+-----------------+ 59 | | T=10000 | 8384ms | 61ms | 339ms | 497ms | 60 | | | | | | | 61 | | Q=1000 | | | | | 62 | | | | | | | 63 | | C=2000 | | | | | 64 | +----------+------------+-----------+------------------+-----------------+ 65 | | T=10000 | 12426ms | 86ms | 586ms | 2460ms | 66 | | | | | | | 67 | | Q=2000 | | | | | 68 | | | | | | | 69 | | C=5000 | | | | | 70 | +----------+------------+-----------+------------------+-----------------+ 71 | | T=30000 | 127000ms | 403ms | 5691ms | 8419ms | 72 | | | | | | | 73 | | Q=6000 | | | | | 74 | | | | | | | 75 | | C=3000 | | | | | 76 | +----------+------------+-----------+------------------+-----------------+ 77 | 78 | 为了更直观的看到每种算法的优劣,我们整合了测试数据,绘制了四种算法和各种参数的图表如下: 79 | 80 | .. only:: html 81 | 82 | .. figure:: images/changed_num_3000.png 83 | :width: 600 84 | :align: center 85 | 86 | .. only:: html 87 | 88 | .. figure:: images/query_num_3000.png 89 | :width: 600 90 | :align: center 91 | 92 | .. only:: html 93 | 94 | .. figure:: images/iters_num_3000.png 95 | :width: 600 96 | :align: center 97 | 98 | 根据结果,我们可以认为 **PrecisionCollisionDetection** 算法在效率和稳定性方面都远远优于其他算法。 99 | -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/2f2s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/2f2s.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/2f2s_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/2f2s_v2.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/2f2s_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/2f2s_v3.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/changed_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/changed_num_3000.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/eighth_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/eighth_merge.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/iters_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/iters_num_3000.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/quarter_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/quarter_merge.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/query_num_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/query_num_3000.png -------------------------------------------------------------------------------- /docs/zh_CN/source/advanced/images/straight_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/advanced/images/straight_merge.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/community/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | ############## 3 | 4 | 5 | Q1: 如何保存对局录像? 6 | *********************************** 7 | 8 | :A1: 9 | 10 | 创建 env 的时候传入 ``playback_settings`` 相关参数,如下图所示。这样在**一局结束**之后会在 ``save_dir`` 目录下保存 ``test.pb`` 文件。通过 GoBigger 提供播放器可以看这局比赛。 11 | 12 | .. code-block:: 13 | 14 | env = create_env('st_t3p2', dict( 15 | playback_settings=dict( 16 | playback_type='by_frame', 17 | by_frame=dict( 18 | save_frame=True, 19 | save_dir='.', 20 | save_name_prefix='test', 21 | ), 22 | ), 23 | )) 24 | 25 | 26 | Q2: 比赛最后的获胜条件是什么? 27 | *********************************** 28 | 29 | :A2: 30 | 31 | 通过计算比赛结束时每个队伍下所有玩家的得分和来进行排序。 32 | 33 | 34 | Q3: 局部视野的大小有范围限制吗? 35 | *********************************** 36 | 37 | :A3: 38 | 39 | 玩家的局部视野的大小由其分身球的相对位置决定。我们设置玩家的最小视野是 36*36 的一个矩阵。随着分身球的分散,玩家的最大视野可以达到全局的程度。 40 | 41 | 42 | Q4: conda环境(使用的推荐的python3.6.8)下安装了 gobigger,实际运行的时候出现 ``libGL error failed to open iris`` 该怎么办 43 | *********************************** 44 | 45 | :A4: 46 | 47 | 是glibc版本过低却安装了高版本的libgl导致的。这里有个相似的问题可以看看 https://askubuntu.com/questions/1352158/libgl-error-failed-to-load-drivers-iris-and-swrast-in-ubuntu-20-04 48 | 49 | 50 | Q5: 该环境中,智能体能否执行出类似于人类玩家的中吐行为?周围自己的小球把孢子吐给中间的球? 51 | *********************************** 52 | 53 | :A5: 在执行吐孢子操作的时候,可以指定方向。我们把和方向有关的参数 ``(x, y)`` 设置为 ``(0, 0)``,则玩家球会逐渐减速,同时方向会慢慢转向质心。之后再执行吐孢子的动作,则会实现中吐。 54 | 55 | .. code-block:: 56 | 57 | action1 = [0, 0, -1] # 停止 58 | action2 = [0, 0, 2] # 吐孢子 59 | 60 | 61 | Q6: 吃荆棘球,自己体积会变大吗?还是说只分裂? 62 | *********************************** 63 | 64 | :A6: 体积也会变大。 65 | -------------------------------------------------------------------------------- /docs/zh_CN/source/index.rst: -------------------------------------------------------------------------------- 1 | .. gobigger documentation master file, created by 2 | sphinx-quickstart on Fri Aug 27 11:46:57 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 欢迎查阅 GoBigger 中文文档! 7 | ========================================= 8 | 9 | 总览 10 | ------------ 11 | 12 | 多智能体对抗作为决策AI中重要的部分,也是强化学习领域的难题之一。为丰富多智能体对抗环境, OpenDILab 开源了一款多智能体对抗竞技游戏环境——Go-Bigger。同时,Go-Bigger 还可作为强化学习环境协助多智能体决策 AI 研究。 13 | 与风靡全球的 `Agar `_ 等游戏类似,在 Go-Bigger 中,玩家(AI)控制地图中的一个或多个圆形球,通过吃食物球和其他比玩家球小的单位来尽可能获得更多重量,并需避免被更大的球吃掉。每个玩家开始仅有一个球,当球达到足够大时,玩家可使其分裂、吐孢子或融合,和同伴完美配合来输出博弈策略,并通过AI技术来操控智能体由小到大地进化,凭借对团队中多智能体的策略控制来吃掉尽可能多的敌人,从而让己方变得更强大并获得最终胜利。 14 | 15 | GoBigger 提供了多种接口供用户方便快捷地与游戏环境进行交互。如果想快速了解游戏,可以通过实时人机对战接口在本地快速地开启一局游戏。同时 GoBigger 还提供了方便的标准 gym.Env 的接口以供研究人员学习他们的策略。用户也可以自定义游戏的初始环境,来进行更多样化的游戏对局。 16 | 17 | 18 | 索引 19 | ===================== 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | :caption: 教程 24 | 25 | installation/index 26 | tutorial/quick_start 27 | tutorial/what_is_gobigger 28 | tutorial/gobigger_engine 29 | tutorial/real_time_interaction_with_game 30 | tutorial/space 31 | tutorial/gobigger_env 32 | tutorial/playback 33 | 34 | .. toctree:: 35 | :maxdepth: 2 36 | :caption: 进阶 37 | 38 | advanced/cfg_intro 39 | advanced/collision 40 | 41 | .. toctree:: 42 | :maxdepth: 2 43 | :caption: 社区 44 | 45 | community/faq 46 | -------------------------------------------------------------------------------- /docs/zh_CN/source/installation/index.rst: -------------------------------------------------------------------------------- 1 | 安装 2 | ############## 3 | 4 | 前置需求 5 | ================= 6 | 7 | 我们已经在以下系统版本中进行过测试: 8 | 9 | * Centos 7 10 | * Windows 10 11 | * MacOS 12 | 13 | 同时,我们推荐使用 Python 版本为 ``3.6.8``。 14 | 15 | 16 | 快速安装 GoBigger 17 | ============================= 18 | 19 | 我们可以通过 PyPI 直接安装: 20 | 21 | .. code-block:: bash 22 | 23 | pip install gobigger 24 | 25 | 同样,也可以通过 conda 进行安装: 26 | 27 | .. code-block:: bash 28 | 29 | conda install -c opendilab gobigger 30 | 31 | 如果想要使用最新的版本,可以通过源码进行安装。首先,通过 Github 下载 GoBigger 源码。 32 | 33 | .. code-block:: bash 34 | 35 | git clone https://github.com/opendilab/GoBigger.git 36 | 37 | 然后,我们从源代码进行安装。 38 | 39 | .. code-block:: bash 40 | 41 | # install for use 42 | # Note: use `--user` option to install the related packages in the user own directory(e.g.: ~/.local) 43 | pip install . --user 44 | 45 | # install for development(if you want to modify GoBigger) 46 | pip install -e . --user 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/gobigger_engine.rst: -------------------------------------------------------------------------------- 1 | GoBigger 引擎设计 2 | #################### 3 | 4 | 总览 5 | =============== 6 | 7 | 本节主要说明了 GoBigger 的引擎设计,其中包括各种球的详细运动逻辑设定。如果读者想要根据 GoBigger 去开发新的环境,或者想要更深入了解 GoBigger 的研发细节,可以仔细阅读本节。为了方便阐述,我们将按顺序介绍每一种球的逻辑设定,并在其中穿插和其他球的交互过程。如果你对 GoBigger 中基础的球单位还不熟悉,建议先查阅上一节(GoBigger是什么)。注意,以下的说明均基于 ``st_t4p3`` 的设置。 8 | 9 | 10 | 所有球的共同点 11 | ============== 12 | 13 | 1. 在 GoBigger 中,你可以看到不同的球有不同的大小。我们定义每个球都有一定的分数 ``score``,并通过分数可以计算出对应的半径 ``radius``。 14 | 15 | .. code-block:: python 16 | 17 | import math 18 | 19 | def score_to_radius(score): 20 | return math.sqrt(score / 100 * 0.042 + 0.15) 21 | 22 | 2. 食物球是不可移动的,但是荆棘球,孢子球,分身球都包含速度属性,也就是说他们都可以移动。 23 | 3. 如果两个球存在可以吃与被吃的关系(例如分身球之间,分身球和所有球,荆棘球和孢子球之间),那么当两个球出现圆心重合的情况时,将会通过判断二者分数来判断是否能发生吞噬。我们规定当大球的分数超过小球的 ``1.3`` 倍时才能发生吞噬。 24 | 4. 所有球在运动过程中圆心都不会超出地图边界。 25 | 5. 我们默认游戏内的 ``FPS`` 是 ``20``。也就是说,一秒内游戏状态会更新 ``20`` 次。同时,默认情况下每次调用 ``env.step`` 会使得环境前进两帧,用户提供的动作会在第一帧做完,然后第二帧赋予空动作。 26 | 27 | 28 | 食物球 29 | =============== 30 | 31 | 1. 食物球是游戏中的中立资源。在 ``st_t4p3`` 的配置中,开局时地图上的食物球数量会有 ``800`` 个,且数量上限为 ``900`` 个。每隔 ``8`` 帧,将会补充 ``(数量上限-当前数量)* 0.01`` 个食物球,并使得食物球的数量不会超过数量上限。 32 | 2. 食物球的初始分数是 ``100``。也就是说,如果一个分身球吃掉了一个食物球,那么这个分身球的分数会增加 ``100``。 33 | 34 | 35 | 荆棘球 36 | =============== 37 | 1. 也称为刺球。荆棘球也是游戏中的中立资源。在 ``st_t4p3`` 的配置中,开局时地图上的荆棘球数量会有 ``9`` 个,且数量上限为 ``12`` 个。每隔 ``120`` 帧,将会补充向上取整 ``(数量上限-当前数量)* 0.2`` 个荆棘球,并使得荆棘球的数量不会超过数量上限。 38 | 2. 荆棘球的初始分数会从 ``10000`` 到 ``15000`` 范围内随机选择。 39 | 3. 当玩家向荆棘球吐孢子球的时候,如果孢子球和荆棘球发生了圆心覆盖,荆棘球会吃掉孢子球,同时往孢子球的运动方向产生一个为 ``10`` 的初速度,并且该速度在 ``20`` 帧内均匀衰减到 ``0``。 40 | 41 | 42 | 孢子球 43 | =============== 44 | 1. 孢子球的大小是固定的,分数固定为 ``1400``。 45 | 2. 孢子球被吐出的时候速度是固定的,为 ``30``,并且该速度在 ``20`` 帧内均匀衰减到 ``0``。 46 | 47 | 48 | 分身球 49 | =============== 50 | 分身球的大小随着不断吃球而变大。分身球的初始分数为 ``1000``。单个玩家最多拥有 ``16`` 个分身球。每个分身球的分数超过 ``3200`` 时才可以使用吐孢子 ``eject`` 技能,超过 ``3600`` 时才可以使用分裂 ``split`` 技能。 51 | 52 | 53 | 速度 54 | --------------- 55 | 分身球的速度由三部分矢量共同作用:玩家操作,多个球存在时导致的向心力,以及分裂或吃荆棘球之后带来的逐渐衰减的速度。玩家操作和向心力带来的加速度在每一帧会乘以每帧时间长度来作为速度的改变量。 56 | 57 | 1. 玩家操作:如动作空间定义的那样,玩家可以提供一个单位圆内的任意一点 ``(x,y)`` 来改变分身球的速度。具体来说,GoBigger 会首先将这一点归一化到单位圆内(如果在圆外则归一化到圆上,否则不做处理),然后乘以权重 ``30`` 来作为加速度。 58 | 2. 向心力:当玩家拥有多个分身球的时候,多个球内部会产生一个向心力,该力指向质心。实际上向心力不会直接使用,会除以半径之后作为真正的向心力,并乘以权重 ``10`` 来作为加速度。 59 | 3. 分裂或吃荆棘球之后带来的逐渐衰减的速度:分身球吃掉荆棘球分裂出来的新的分身球会拥有一个分裂后新的初始速度。这个速度的模长为和分裂后的半径有关,具体为 ``(260 - 20 * radius) / 7``,方向背离圆心。该速度会在 ``14`` 帧内均匀衰减到 ``0``。如果分身球是通过分裂技能得到,那这个初始速度的模长的计算公式为 ``(95 + 19 * radius) / 7``。 60 | 4. 玩家操作和向心力带来的速度会受到速度上限的限制。速度上限和球的半径有关,并会将玩家操作和向心力中较大之一来作为 ``ratio`` 对速度上限进行调整。具体公式为 ``(2.35 + 5.66 / radius) * ratio``。 61 | 62 | 63 | 吃荆棘球 64 | --------------- 65 | 1. 分身球每次吃掉荆棘球的时候,会分裂出不超过上限 ``10`` 个新的分身球。举个例子,如果玩家当前只有两个分身球,而其中一个分身球吃掉了一个荆棘球,那他会分裂出新的 ``10`` 个分身球,因此此时该玩家总共有 ``12`` 个分身球;如果玩家当前有 ``10`` 个分身球,而其中一个分身球吃掉了一个荆棘球,那他会分裂出新的 ``6`` 个分身球,因此此时该玩家总共有 ``16`` 个分身球。 66 | 2. 分身球吃掉荆棘球分裂出来的新球的最大分数为 ``5000``。举个例子,分身球当前分数是 ``23000``,他吃掉了一个分数为 ``10000`` 的荆棘球,那么他的分数就会变成 ``33000``。与此同时,这个分身球会分裂出新的 ``10`` 个分身球,按照均匀分配,每个分身球的分数为 ``3000``。 67 | 3. 吃荆棘球分裂之后,新增的球的位置均匀分布在周围,且总会有新增的球分身处于原始球右侧水平位置。 68 | 69 | 70 | 分裂 71 | --------------- 72 | 1. 分裂出来新的分身球会在玩家指定的方向上,如果没有指定方向,则会出现在原始球的运动方向上。 73 | 2. 分裂技能会将原始球的分数均分到两个分裂后的球上。 74 | 3. 分裂出来新的分身球也会拥有原始分身球的速度。 75 | 4. 无论是分裂后还是吃荆棘球后,玩家的分身球(包括触发分裂和吃荆棘球,以及这俩操作后新增的分身球)都会进入长度为 ``20`` 秒的冷却阶段。在冷却阶段内的分身球无法触发和自己其他的分身球合并的操作。此外,在分身球完成一次合并之后,会重新进入冷却阶段。 76 | 77 | 78 | 吐孢子 79 | --------------- 80 | 1. 分身球执行吐孢子操作后产生的孢子球会出现在玩家指定的方向上,如果没有指定方向,则会出现在分身球的运动方向上。 81 | 2. 分身球每吐一个孢子球,相当于把自身分数中割离了 ``1400`` 分到新的孢子球上。 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/gobigger_env.rst: -------------------------------------------------------------------------------- 1 | GoBigger 环境 2 | #################### 3 | 4 | 总览 5 | =============== 6 | 7 | 本节主要说明了 GoBigger 的环境设计,其中包括给定好的环境的定义,以及如何自定义生成对应的环境。 8 | 9 | 10 | 已经定义好的环境 11 | ======================= 12 | 13 | GoBigger 定义了一些基础环境,可以通过下面的代码生成这些基础环境 14 | 15 | .. code-block:: python 16 | 17 | from gobigger.envs import create_env 18 | env = create_env('st_t4p3') 19 | 20 | 其中,``create_env()`` 接收的第一个参数是一个字符串,是我们已经定义好的环境的名称。类似的,除了 ``st_t4p3`` 以外,我们还有 ``st_t2p2``,``st_t3p2`` 可供选择。下面详细介绍一下各个环境的区别。 21 | 22 | 1. ``st_t4p3``: 创建了一个有四支队伍,每支队伍三个玩家的游戏场景。这是我们定义的标准游戏场景,包括其中的各种球的刷新速度等参数都已经调节至一个比较健壮的水平。用户可以使用这个环境来解决一些复杂空间场景下的合作和对抗问题。 23 | 24 | 2. ``st_t2p2``: 考虑到 ``st_t4p3`` 可能过于庞大,因此我们提供了一个更小的游戏场景。在这个场景内,只有两支队伍,每支队伍两个玩家。同样,地图尺寸,食物球和荆棘球的数量也受到了相应的削减。此外,为了缩减玩家前期发育的时间,我们在 ``st_t2p2`` 将玩家第一次出生时的分数设置成了 13000 来使得玩家可以快速进行对抗,而这个值在 ``st_t4p3`` 中是 1000。 25 | 26 | 3. ``st_t3p2``: 这是一个间于 ``st_t2p2`` 和 ``st_t4p3`` 的中等环境,有三支队伍,每支队伍两个玩家。和 ``st_t2p2`` 一样,也设置了较高的出生分数来减少发育时间。 27 | 28 | 此外,上述环境默认都是每两帧做一个动作(一秒20帧)。如果想要更长的动作间隔,可以通过下面代码设置: 29 | 30 | .. code-block:: python 31 | 32 | from gobigger.envs import create_env 33 | env = create_env('st_t4p3', step_mul=10) 34 | 35 | 此外,我们还有更多已经定义好的环境,如下: 36 | 37 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 38 | | Name | Agents Size | Map Size | Food | Thorn | Init Size | Limited Frame | 39 | +==========+==============+===========+==============+==========+============+================+ 40 | | Small Maps | 41 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 42 | | st_t1p1 | 1x1 | 32x32 | [65,75] | [1,2] | 1000 | 3600(3min) | 43 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 44 | | st_t1p2 | 1x2 | 48x48 | [130,150] | [2,3] | 1000 | 3600(3min) | 45 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 46 | | st_t2p1 | 2x1 | 48x48 | [130,150] | [2,3] | 1000 | 3600(3min) | 47 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 48 | | st_t2p2 | 2x2 | 64x64 | [260,300] | [3,4] | 13000 | 3600(3min) | 49 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 50 | | st_t3p2 | 3x2 | 88x88 | [500,560] | [5,6] | 13000 | 3600(3min) | 51 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 52 | | Large Maps | 53 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 54 | | st_t4p3 | 4x3 | 128x128 | [800,900] | [9,12] | 1000 | 14400(12min) | 55 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 56 | | st_t5p3 | 5x3 | 128x128 | [900,1000] | [10,12] | 1000 | 14400(12min) | 57 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 58 | | st_t5p4 | 5x4 | 144x144 | [900,1000] | [10,12] | 1000 | 14400(12min) | 59 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 60 | | st_t6p4 | 6x4 | 144x144 | [1000,1100] | [11,13] | 1000 | 14400(12min) | 61 | +----------+--------------+-----------+--------------+----------+------------+----------------+ 62 | 63 | 64 | 自定义生成对应的环境 65 | ======================= 66 | 67 | GoBigger 丰富的配置文件设计使得用户可以很方便地设置游戏内的每一个细节。 68 | 69 | 如果想要在某个我们已经定义好的环境的基础上进行简单修改,例如在 ``st_t2p2`` 修改一局时长,可以如下: 70 | 71 | .. code-block:: python 72 | 73 | from gobigger.envs import create_env 74 | env = create_env('st_t2p2', dict( 75 | frame_limit=10*60*20, 76 | )) 77 | 78 | 这样,开启的新环境就会在 ``st_t2p2`` 的基础上变成了 10 分钟一局。 79 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eat_food.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eat_food.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eat_player.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eat_player.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eject.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eject.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eject_and_move.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eject_and_move.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eject_cross.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eject_cross.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eject_merger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eject_merger.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/eject_to_thorns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/eject_to_thorns.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/fast_eat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/fast_eat.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/merge_quickly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/merge_quickly.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/on_thorns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/on_thorns.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/overview.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/split.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/split_eat_all.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/split_eat_all.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/images/split_merge.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/docs/zh_CN/source/tutorial/images/split_merge.gif -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | 教程 2 | ########## 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | quick_start 8 | what_is_gobigger 9 | gobigger_engine 10 | real_time_interaction_with_game 11 | space 12 | gobigger_env 13 | playback 14 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/playback.rst: -------------------------------------------------------------------------------- 1 | 回放系统 2 | ############## 3 | 4 | GoBigger的回放系统支持三种选择,可以通过环境的配置文件来进行选择。涉及到的配置项如下: 5 | 6 | .. code-block:: python 7 | 8 | config = dict( 9 | ... 10 | playback_settings=dict( 11 | playback_type='none', # ['none', 'by_video', 'by_frame'] 12 | by_video=dict( 13 | save_video=False, 14 | save_fps=10, 15 | save_resolution=552, 16 | save_all=True, 17 | save_partial=False, 18 | save_dir='.', 19 | save_name_prefix='test', 20 | ), 21 | by_frame=dict( 22 | save_frame=False, 23 | save_all=True, 24 | save_partial=False, 25 | save_dir='.', 26 | save_name_prefix='test', 27 | ), 28 | by_action=dict( 29 | save_action=False, 30 | save_dir='.', 31 | save_name_prefix='test', 32 | ), 33 | ), 34 | ... 35 | ) 36 | 37 | ``playback_type`` 可以是 ``['none', 'by_video', 'by_frame']`` 中的其中一种。其中, 38 | 39 | * ``none``: 代表不需要存回放 40 | * ``by_video``: 代表直接保存录像,保存文件后缀是 ``.mp4``。一般来说,``st_t4p3`` 环境存下来的录像在 80M 左右。 41 | * ``by_frame``: 代表存每一帧的变化量,保存文件后缀是 ``.pb``。一般来说,``st_t4p3`` 环境存下来文件在 25M 左右。 42 | 43 | 44 | 直接保存录像 45 | -------------- 46 | 47 | 如果选择 ``playback_type='by_video'``,具体的配置项可以像下面这样: 48 | 49 | .. code-block:: python 50 | 51 | env = create_env('st_t4p3', dict( 52 | playback_settings=dict( 53 | playback_type='by_video', 54 | by_video=dict( 55 | save_video=True, 56 | save_dir='.', # 需要保存录像的目录位置 57 | save_name_prefix='test', # 保存录像名字的前缀 58 | ), 59 | ), 60 | )) 61 | 62 | 63 | 直接保存pb文件 64 | -------------- 65 | 66 | 如果选择 ``playback_type='by_frame'``,具体的配置项可以像下面这样: 67 | 68 | .. code-block:: python 69 | 70 | env = create_env('st_t4p3', dict( 71 | playback_settings=dict( 72 | playback_type='by_frame', 73 | by_frame=dict( 74 | save_frame=True, 75 | save_dir='.', # 需要保存录像的目录位置 76 | save_name_prefix='test', # 保存录像名字的前缀 77 | ) 78 | ), 79 | )) 80 | 81 | 得到保存后的 ``.pb`` 文件之后,需要通过我们给定的播放器来查看。在命令行中执行下面的命令来打开播放器。 82 | 83 | .. code-block:: python 84 | 85 | python -m gobigger.bin.replayer 86 | 87 | 打开播放器之后,需要选择你想要查看的 ``.pb`` 文件。然后就可以开始看了。播放器支持倍速播放,包括2倍,4倍,8倍(通过点击左下角的按钮)。同时支持拖动进度条。 88 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/quick_start.rst: -------------------------------------------------------------------------------- 1 | 快速开始 2 | ############## 3 | 4 | 通过代码与游戏环境进行交互 5 | ================================== 6 | 7 | 在安装完成之后,可以用以下代码快速实现与游戏环境进行交互: 8 | 9 | .. code-block:: python 10 | 11 | import random 12 | from gobigger.envs import create_env 13 | 14 | env = create_env('st_t2p2') 15 | obs = env.reset() 16 | for i in range(1000): 17 | actions = {0: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 18 | 1: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 19 | 2: [random.uniform(-1, 1), random.uniform(-1, 1), 0], 20 | 3: [random.uniform(-1, 1), random.uniform(-1, 1), 0]} 21 | obs, rew, done, info = env.step(actions) 22 | print('[{}] leaderboard={}'.format(i, obs[0]['leaderboard'])) 23 | if done: 24 | print('finish game!') 25 | break 26 | env.close() 27 | 28 | 在上述代码中,首先构建了环境,然后通过 ``env.step()`` 完成游戏每一步的进行,并获取到对应的 ``observation``,``reward``,``done``,``info`` 等信息。执行之后,将会得到类似下面的输出,给出了每一帧排行榜信息。 29 | 30 | .. code-block:: 31 | 32 | [0] leaderboard={0: 3000, 1: 3100.0} 33 | [1] leaderboard={0: 3000, 1: 3100.0} 34 | [2] leaderboard={0: 3000, 1: 3100.0} 35 | [3] leaderboard={0: 3000, 1: 3100.0} 36 | [4] leaderboard={0: 3000, 1: 3100.0} 37 | [5] leaderboard={0: 3000, 1: 3100.0} 38 | [6] leaderboard={0: 3000, 1: 3100.0} 39 | [7] leaderboard={0: 3000, 1: 3100.0} 40 | [8] leaderboard={0: 3000, 1: 3100.0} 41 | [9] leaderboard={0: 3000, 1: 3100.0} 42 | [10] leaderboard={0: 3000, 1: 3100.0} 43 | ... 44 | 45 | 46 | 自定义游戏环境 47 | ============================ 48 | 49 | 用户也可以选择通过修改配置 cfg,并通过我们提供的 ``gobigger.envs.create_env_custom`` 方法来自定义游戏环境。``gobigger.envs.create_env_custom`` 方法接收两个参数,第一个参数是 ``type``,可选值为 ``st`` 或 ``sp``,分别代表标准比赛模式,和独立动作比赛模式。关于两种模式的介绍具体可以看。以下我们基于标准比赛模式举几个简单的例子。 50 | 51 | 修改游戏中的队伍数量和玩家数量 52 | ------------------------------------ 53 | 54 | 如果用户想要在游戏中存在 6 个队伍,每个队伍中含有 2 名玩家,那么可以修改 ``team_num`` 和 ``player_num_per_team``。 55 | 56 | .. code-block:: python 57 | 58 | from gobigger.envs import create_env_custom 59 | 60 | env = create_env_custom(type='st', cfg=dict( 61 | team_num=6, 62 | player_num_per_team=2 63 | )) 64 | 65 | 修改游戏时长 66 | ------------------------------------ 67 | 68 | 游戏内默认每秒20帧。如果用户想要将游戏时长设置为 20 分钟,即 24000 帧,可以修改 ``frame_limit``。 69 | 70 | .. code-block:: python 71 | 72 | from gobigger.envs import create_env_custom 73 | 74 | env = create_env_custom(type='st', cfg=dict( 75 | frame_limit=24000 76 | )) 77 | 78 | 修改游戏地图大小 79 | ------------------------------------ 80 | 81 | 如果用户想要拥有一个更大的地图,可以修改 ``map_width`` 和 ``map_height``。注意更大的地图可能会导致 ``step`` 速度变慢。 82 | 83 | .. code-block:: python 84 | 85 | from gobigger.envs import create_env_custom 86 | 87 | env = create_env_custom(type='st', cfg=dict( 88 | map_width=1000, 89 | map_height=1000, 90 | )) 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/real_time_interaction_with_game.rst: -------------------------------------------------------------------------------- 1 | 实时游玩 2 | ########################################## 3 | 4 | GoBigger 允许用户在个人电脑中实时游玩。同时也提供了多种游戏模式供用户选择。 5 | 6 | .. note:: 7 | 8 | 如果你还在使用 GoBigger v0.1.x 系列,建议升级以获得更好的体验。可以使用 ``pip install --upgrade gobigger`` 来快速获取最新版本的 GoBigger。 9 | 10 | 11 | 标准比赛模式+部分视野 12 | ======================= 13 | 14 | 该模式下,玩家只能看到周围一定范围内的视野。可以通过以下代码启动游戏: 15 | 16 | .. code-block:: bash 17 | 18 | python -m gobigger.bin.play --mode st --vision-type partial 19 | 20 | 在本模式中,鼠标可以用来操控玩家控制的球,三个技能分别是 ``Q``,``W``。``Q`` 技能是在移动方向上吐孢子,``W`` 技能是将用户的球进行分裂。玩家的视野由球的相对位置决定。尝试分散玩家的球,这样可以获得更大的视野。 21 | 22 | 23 | 标准比赛模式+全局视野 24 | ======================= 25 | 26 | 可以通过以下代码启动游戏: 27 | 28 | .. code-block:: bash 29 | 30 | python -m gobigger.bin.play --mode st --vision-type full 31 | 32 | 33 | 独立动作比赛模式+部分视野 34 | ======================= 35 | 36 | 独立动作比赛模式下,玩家的每个球都单独接受一个动作。但是由于比较难操控,我们在这个模式下还是只根据鼠标和键盘接收一个动作,并将这个动作赋给玩家的所有球。可以通过以下代码来启动游戏: 37 | 38 | .. code-block:: bash 39 | 40 | python -m gobigger.bin.play --mode sp --vision-type partial 41 | -------------------------------------------------------------------------------- /docs/zh_CN/source/tutorial/what_is_gobigger.rst: -------------------------------------------------------------------------------- 1 | GoBigger 是什么 2 | #################### 3 | 4 | 总览 5 | =============== 6 | 7 | GoBigger 是一个类 `Agar `_ 游戏,后者是一款风靡全球的游戏。在 GoBigger 中,玩家需要在一张平面图中操控他的分身球。在地图中,还会有食物球,荆棘球,孢子球,以及其他的玩家的分身球。在有限的时间内,玩家需要通过吃掉其他的球来吸收他们的质量,并转化为自己的质量。在比赛结束的时候,质量最大的玩家将会获得胜利。为了提高游戏的对抗性,玩家还可以吃掉其他比他小的玩家来快速发育。因此,在游戏中,玩家需要兼顾快速发育和躲避风险,从而一步步获取到最多的质量以获得游戏的胜利。 8 | 9 | .. only:: html 10 | 11 | .. figure:: images/overview.gif 12 | :width: 600 13 | :align: center 14 | 15 | 玩家正在游戏中操控分身球。 16 | 17 | 为了更方便的介绍游戏规则,我们首先介绍 GoBigger 中出现的各种球。 18 | 19 | 游戏中的球 20 | =============== 21 | * 食物球 22 | 23 | 食物球是游戏中的中立资源。他们不属于任何玩家。他们会在游戏中源源不断地被补充进来,并且不会改变位置直到被吃掉为止。如果某个玩家操控的分身球吃掉了一个食物球,那么食物球的质量将会被传递到了分身球中。地图中的食物球数量会存在上限。 24 | 25 | .. only:: html 26 | 27 | .. figure:: images/eat_food.gif 28 | :width: 300 29 | :align: center 30 | 31 | 玩家操控的分身球吃掉了食物球。 32 | 33 | 34 | * 荆棘球 35 | 36 | 荆棘球也是游戏中的中立资源。与食物球不同在于,荆棘球一般拥有更大的尺寸,同时地图中的荆棘球数量上限会比食物球少很多。如果一个玩家的分身球吃掉了荆棘球(前提是分身球比荆棘球大),荆棘球的质量会被传递到分身球内,同时分身球会被引爆并分裂成多个小的分身球。在游戏中,分裂产生的小分身球会呈放射状发射出去,并在短时间内速度衰减。因此,虽然吃掉荆棘球是一个很好的发育方式,但是带来的分裂效果会使得玩家的分身球处在一个危险的境地中(可能会被其他玩家吃掉)。 37 | 38 | 荆棘球的另一个特点是可以被玩家通过吐孢子的方式移动。如果某个玩家的分身球对着荆棘球进行吐孢子,那么荆棘球将会吃掉这个孢子,获得质量,并朝着孢子的移动方向前进一小段距离。因此,玩家可以通过移动荆棘球来让更大的玩家的分身球引爆,从而寻找机会吃掉分裂出来的小球。 39 | 40 | .. only:: html 41 | 42 | .. figure:: images/on_thorns.gif 43 | :width: 300 44 | :align: center 45 | 46 | 玩家操控的分身球吃掉了荆棘球并被引爆。 47 | 48 | * 孢子球 49 | 50 | 孢子球是由玩家的分身球通过吐孢子的技能产生的。他们会获得一个初速度并移动一小段距离,然后静止在原地。孢子球可以被比他大的玩家和荆棘球吃掉。 51 | 52 | .. only:: html 53 | 54 | .. figure:: images/eject.gif 55 | :width: 300 56 | :align: center 57 | 58 | 玩家操控的分身球正在吐孢子。 59 | 60 | * 玩家的分身球 61 | 62 | 玩家的分身球就是玩家在游戏中操控的球。玩家可以对它的运动方向进行任意的改变,还可以吃掉比它小的其他球。在吃掉其他球的瞬间,玩家的分身球会获取到被吃球的质量,并且半径变大。为了增强游戏的可操作性,每个玩家的分身球都会有以下两种技能: 63 | 64 | .. only:: html 65 | 66 | .. figure:: images/eat_player.gif 67 | :width: 300 68 | :align: center 69 | 70 | 一个玩家正在吃掉另一个玩家的分身球。 71 | 72 | * 吐孢子 73 | 74 | 吐孢子可以帮助玩家的分身球快速减少体积。体积越小,移动速度的上限将会越高。当某个分身球吐孢子时,孢子会有一个初速度,并在一定时间内速度衰减为零。 75 | 76 | .. only:: html 77 | 78 | .. figure:: images/eject_to_thorns.gif 79 | :width: 300 80 | :align: center 81 | 82 | 玩家朝着荆棘球吐孢子。 83 | 84 | * 分裂 85 | 86 | 分裂技能可以帮助玩家分裂成相同大小的两部分。分裂之后,由于单个分身球的体积变小,对应的移动速度将会提高。分裂也是有代价的。请注意,在分裂之后,玩家的分身球会进入冷却期。在冷却期期间,分身球无法被自身的其他分身球合并。 87 | 88 | .. only:: html 89 | 90 | .. figure:: images/split.gif 91 | :width: 300 92 | :align: center 93 | 94 | 玩家正在进行分裂。 95 | 96 | 97 | 游戏规则 98 | =============== 99 | 100 | 如下,有一些值得注意的规则: 101 | 102 | 1. 玩家的分身球的质量会不断减少。GoBigger 设置了一个衰减系数,玩家球每秒的实际衰减系数会在默认衰减系数的基础上乘以球本身的半径。因此衰减在玩家球质量非常大的时候体现地更为明显。 103 | 104 | 2. 如果玩家的所有球都被吃掉了,那么他会立即随机在地图中重生。 105 | 106 | 3. 玩家的视野大小是由玩家的分身球位置所决定的。我们计算玩家所有分身球的质心,并获取到它的最小外接矩形,并在此基础上进行放大来决定该玩家的视野范围。同时,我们也会指定玩家的最小视野范围。玩家的分身球的相对距离越远,所能看到的视野范围将会越大。 107 | 108 | 4. 玩家的每个分身球都会根据其半径大小存在一个速度上限。在游戏中,半径越大,移动速度将会越慢。 109 | 110 | 111 | 高级操作 112 | ============================== 113 | 114 | .. only:: html 115 | 116 | .. figure:: images/merge_quickly.gif 117 | :width: 320 118 | :align: center 119 | 120 | 朝着中心吐孢子。 121 | 122 | .. only:: html 123 | 124 | .. figure:: images/split_eat_all.gif 125 | :width: 320 126 | :align: center 127 | 128 | 通过分裂快速吃掉其他玩家。 129 | 130 | .. only:: html 131 | 132 | .. figure:: images/fast_eat.gif 133 | :width: 320 134 | :align: center 135 | 136 | 快速吃掉食物球。 137 | 138 | .. only:: html 139 | 140 | .. figure:: images/eject_merger.gif 141 | :width: 422 142 | :align: center 143 | 144 | 聚集质量。 145 | -------------------------------------------------------------------------------- /gobigger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/gobigger/__init__.py -------------------------------------------------------------------------------- /gobigger/agents/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_agent import BaseAgent 2 | from .bot_agent import BotAgent 3 | -------------------------------------------------------------------------------- /gobigger/agents/base_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | 5 | class BaseAgent: 6 | ''' 7 | Overview: 8 | The base class of all agents 9 | ''' 10 | def __init__(self): 11 | pass 12 | 13 | def step(self, obs): 14 | raise NotImplementedError 15 | 16 | -------------------------------------------------------------------------------- /gobigger/agents/tests/test_base_agent.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import time 6 | import random 7 | import numpy as np 8 | import cv2 9 | import pygame 10 | 11 | from gobigger.agents import BaseAgent 12 | from gobigger.utils import Border 13 | from gobigger.server import Server 14 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 15 | 16 | logging.basicConfig(level=logging.DEBUG) 17 | 18 | 19 | @pytest.mark.unittest 20 | class TestBaseAgent: 21 | 22 | def test_init(self): 23 | base_agent = BaseAgent() 24 | assert True 25 | 26 | def test_step(self): 27 | base_agent = BaseAgent() 28 | with pytest.raises(Exception) as e: 29 | base_agent.step(obs=None) 30 | 31 | 32 | -------------------------------------------------------------------------------- /gobigger/agents/tests/test_bot_agent.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import time 6 | import random 7 | import numpy as np 8 | import cv2 9 | import pygame 10 | 11 | from gobigger.agents import BotAgent 12 | from gobigger.utils import Border 13 | from gobigger.server import Server 14 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 15 | 16 | logging.basicConfig(level=logging.DEBUG) 17 | 18 | 19 | @pytest.mark.unittest 20 | class TestBotAgent: 21 | 22 | def test_step(self): 23 | server = Server(dict( 24 | team_num=4, 25 | player_num_per_team=3, 26 | frame_limit=20, 27 | )) 28 | server.reset() 29 | bot_agents = [] 30 | for index, player in enumerate(server.player_manager.get_players()): 31 | bot_agents.append(BotAgent(player.player_id, level=index%3+1)) 32 | logging.debug('players init: {}'.format(player.player_id)) 33 | time_obs = 0 34 | time_step = 0 35 | time_fill_all = 0 36 | time_get_rectangle = 0 37 | time_get_clip = 0 38 | time_cvt = 0 39 | time_overlap = 0 40 | for i in range(100): 41 | t1 = time.time() 42 | obs = server.obs() 43 | t2 = time.time() 44 | if i % 4 == 0: 45 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 46 | else: 47 | actions = None 48 | t3 = time.time() 49 | finish_flag = server.step(actions=actions) 50 | t4 = time.time() 51 | tmp_obs = t2-t1 52 | tmp_step = t4-t3 53 | time_obs += tmp_obs 54 | time_step += tmp_step 55 | logging.debug('{} {} obs: {:.3f} / {:.3f}, step: {:.3f} / {:.3f}'\ 56 | .format(i, server.last_frame_count, tmp_obs, time_obs/(i+1), tmp_step, time_step/(i+1))) 57 | 58 | if finish_flag: 59 | logging.debug('Game Over') 60 | break 61 | server.close() 62 | -------------------------------------------------------------------------------- /gobigger/balls/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_ball import BaseBall 2 | from .food_ball import FoodBall 3 | from .spore_ball import SporeBall 4 | from .thorns_ball import ThornsBall 5 | from .clone_ball import CloneBall -------------------------------------------------------------------------------- /gobigger/balls/food_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from easydict import EasyDict 3 | 4 | from gobigger.utils import format_vector, add_score, Border, deep_merge_dicts 5 | from .base_ball import BaseBall 6 | 7 | 8 | class FoodBall(BaseBall): 9 | """ 10 | Overview: 11 | - characteristic: 12 | * Can't move, can only be eaten, randomly generated 13 | """ 14 | @staticmethod 15 | def default_config(): 16 | cfg = BaseBall.default_config() 17 | cfg.update(dict( 18 | score_min=0.5, 19 | score_max=0.5, 20 | )) 21 | return EasyDict(cfg) 22 | 23 | def __init__(self, ball_id, position, score, border, **kwargs): 24 | super(FoodBall, self).__init__(ball_id, position, score=score, border=border, **kwargs) 25 | self.check_border() 26 | 27 | def move(self, direction, duration): 28 | logging.debug('FoodBall can not move') 29 | return 30 | 31 | def eat(self, ball): 32 | logging.debug('FoodBall can not eat others') 33 | return 34 | 35 | def save(self): 36 | return [self.position.x, self.position.y, self.radius] 37 | -------------------------------------------------------------------------------- /gobigger/balls/spore_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from easydict import EasyDict 3 | from pygame.math import Vector2 4 | import math 5 | 6 | from gobigger.utils import format_vector, add_score, Border, deep_merge_dicts 7 | from .base_ball import BaseBall 8 | 9 | 10 | class SporeBall(BaseBall): 11 | """ 12 | Overview: 13 | Spores spit out by the player ball 14 | - characteristic: 15 | * Can't move actively 16 | * can not eat 17 | * Can be eaten by CloneBall and ThornsBall 18 | * There is an initial velocity at birth, and it decays to 0 within a period of time 19 | """ 20 | @staticmethod 21 | def default_config(): 22 | cfg = BaseBall.default_config() 23 | cfg.update(dict( 24 | score_init=1.5, 25 | vel_init=50, 26 | vel_zero_frame=10, 27 | )) 28 | return EasyDict(cfg) 29 | 30 | def __init__(self, ball_id, position, border, score, direction=Vector2(0,0), owner=-1, **kwargs): 31 | # init other kwargs 32 | kwargs = EasyDict(kwargs) 33 | cfg = SporeBall.default_config() 34 | cfg = deep_merge_dicts(cfg, kwargs) 35 | super(SporeBall, self).__init__(ball_id, position, score=score, border=border, **cfg) 36 | self.score_init = cfg.score_init 37 | self.vel_init = cfg.vel_init 38 | self.vel_zero_frame = cfg.vel_zero_frame 39 | # normal kwargs 40 | self.direction = direction.normalize() 41 | self.vel = self.vel_init * self.direction 42 | self.vel_piece = self.vel / self.vel_zero_frame 43 | self.owner = owner 44 | self.move_frame = 0 45 | # reset score 46 | if self.score != self.score_init: 47 | self.set_score(self.score_init) 48 | self.moving = True 49 | self.check_border() 50 | 51 | def move(self, direction=None, duration=0.05): 52 | assert direction is None 53 | assert duration > 0 54 | if self.moving: 55 | self.position = self.position + self.vel * duration 56 | self.move_frame += 1 57 | if self.move_frame < self.vel_zero_frame: 58 | self.vel -= self.vel_piece 59 | else: 60 | self.vel = Vector2(0, 0) 61 | self.vel_piece = Vector2(0, 0) 62 | self.moving = False 63 | self.check_border() 64 | return True 65 | 66 | def eat(self, ball): 67 | logging.debug('SporeBall can not eat others') 68 | return 69 | 70 | def save(self): 71 | return [self.position.x, self.position.y, self.radius] 72 | -------------------------------------------------------------------------------- /gobigger/balls/tests/test_base_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.balls import BaseBall 7 | from gobigger.utils import Border 8 | 9 | logging.basicConfig(level=logging.DEBUG) 10 | 11 | @pytest.mark.unittest 12 | class TestBaseBall: 13 | 14 | def test_init(self): 15 | border = Border(0, 0, 100, 100) 16 | position = Vector2(10, 10) 17 | ball_id = uuid.uuid1() 18 | base_ball = BaseBall(ball_id, position, border=border, score=6) 19 | assert True 20 | 21 | def test_judge_in_rectangle(self): 22 | border = Border(0, 0, 800, 800) 23 | position = Vector2(400, 400) 24 | ball_id = uuid.uuid1() 25 | base_ball = BaseBall(ball_id, position, border=border, score=6) 26 | rectangle = [300, 300, 500, 500] 27 | assert base_ball.judge_in_rectangle(rectangle) 28 | 29 | def test_move(self): 30 | border = Border(0, 0, 800, 800) 31 | position = Vector2(400, 400) 32 | ball_id = uuid.uuid1() 33 | base_ball = BaseBall(ball_id, position, border=border, score=6) 34 | with pytest.raises(Exception) as e: 35 | base_ball.move(direction=None, duration=None) 36 | 37 | def test_eat(self): 38 | border = Border(0, 0, 800, 800) 39 | position = Vector2(400, 400) 40 | ball_id = uuid.uuid1() 41 | base_ball = BaseBall(ball_id, position, border=border, score=6) 42 | with pytest.raises(Exception) as e: 43 | base_ball.eat(ball=None) 44 | 45 | def test_op_override(self): 46 | border = Border(0, 0, 800, 800) 47 | base_ball_1 = BaseBall(uuid.uuid1(), border.sample(), border=border, score=6) 48 | base_ball_2 = BaseBall(uuid.uuid1(), border.sample(), border=border, score=7) 49 | assert not base_ball_1 == base_ball_2 50 | assert base_ball_1 < base_ball_2 51 | assert not base_ball_1 > base_ball_2 52 | -------------------------------------------------------------------------------- /gobigger/balls/tests/test_food_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | from easydict import EasyDict 6 | 7 | from gobigger.balls import FoodBall 8 | from gobigger.utils import Border 9 | 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | @pytest.mark.unittest 13 | class TestFoodBall: 14 | 15 | def test_naive(self): 16 | ball_id = uuid.uuid1() 17 | border = Border(0, 0, 100, 100) 18 | position = Vector2(10, 10) 19 | food_ball = FoodBall(ball_id, position, border=border, score=1) 20 | assert True 21 | 22 | def test_default_config(self): 23 | assert isinstance(FoodBall.default_config(), EasyDict) 24 | 25 | def test_move(self): 26 | ball_id = uuid.uuid1() 27 | border = Border(0, 0, 100, 100) 28 | position = Vector2(10, 10) 29 | food_ball = FoodBall(ball_id, position, border=border, score=1) 30 | food_ball.move(direction=None, duration=None) 31 | 32 | def test_eat(self): 33 | ball_id = uuid.uuid1() 34 | border = Border(0, 0, 100, 100) 35 | position = Vector2(10, 10) 36 | food_ball = FoodBall(ball_id, position, border=border, score=1) 37 | food_ball.eat(ball=None) -------------------------------------------------------------------------------- /gobigger/balls/tests/test_spore_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.balls import SporeBall 7 | from gobigger.utils import Border 8 | 9 | logging.basicConfig(level=logging.DEBUG) 10 | 11 | @pytest.mark.unittest 12 | class TestSporesBall: 13 | 14 | def test_move(self): 15 | ball_id = uuid.uuid1() 16 | border = Border(0, 0, 1000, 1000) 17 | position = Vector2(100, 100) 18 | direction = Vector2(1, 0) 19 | spore_ball = SporeBall(ball_id, position, border=border, score=2, direction=direction) 20 | logging.debug('direction={}, position={}, vel={}, move_frame={}' 21 | .format(spore_ball.direction, spore_ball.position, spore_ball.vel, spore_ball.move_frame)) 22 | for i in range(10): 23 | spore_ball.move(duration=0.05) 24 | logging.debug('[{}] direction={}, position={}, vel={}, move_frame={}' 25 | .format(i, spore_ball.direction, spore_ball.position, spore_ball.vel, spore_ball.move_frame)) 26 | assert True 27 | 28 | def test_eat(self): 29 | ball_id = uuid.uuid1() 30 | border = Border(0, 0, 1000, 1000) 31 | position = Vector2(100, 100) 32 | direction = Vector2(1, 0) 33 | spore_ball = SporeBall(ball_id, position, border=border, score=2, direction=direction) 34 | spore_ball.eat(ball=None) -------------------------------------------------------------------------------- /gobigger/balls/tests/test_thorns_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.balls import BaseBall, ThornsBall, SporeBall 7 | from gobigger.utils import Border 8 | 9 | logging.basicConfig(level=logging.DEBUG) 10 | 11 | @pytest.mark.unittest 12 | class TestThornsBall: 13 | 14 | def test_init(self): 15 | ball_id = uuid.uuid1() 16 | border = Border(0, 0, 100, 100) 17 | position = Vector2(10, 10) 18 | thorns_ball = ThornsBall(ball_id, position, border=border, score=4) 19 | assert True 20 | 21 | def test_eat_move(self): 22 | ball_id = uuid.uuid1() 23 | border = Border(0, 0, 1000, 1000) 24 | thorns_position = Vector2(100, 100) 25 | thorns_score = ThornsBall.default_config().score_min 26 | thorns_ball = ThornsBall(ball_id, thorns_position, border=border, score=thorns_score) 27 | 28 | ball_id = uuid.uuid1() 29 | spore_position = Vector2(100, 100) 30 | spore_score = SporeBall.default_config().score_init 31 | direction = Vector2(1, 0) 32 | spore_ball = SporeBall(ball_id, spore_position, border=border, score=spore_score, direction=direction) 33 | 34 | logging.debug('=========================== before eat =============================') 35 | logging.debug('[thorns] position={}, score={}, vel={}, move_frame={}' 36 | .format(thorns_ball.position, thorns_ball.score, thorns_ball.vel, thorns_ball.move_frame)) 37 | logging.debug('[spore] position={}, score={}, vel={}' 38 | .format(spore_ball.position, spore_ball.score, spore_ball.vel)) 39 | thorns_ball.eat(spore_ball) 40 | logging.debug('=========================== after eat =============================') 41 | logging.debug('[thorns] position={}, score={}, vel={}, move_frame={}' 42 | .format(thorns_ball.position, thorns_ball.score, thorns_ball.vel, thorns_ball.move_frame)) 43 | logging.debug('[spore] position={}, score={}, vel={}' 44 | .format(spore_ball.position, spore_ball.score, spore_ball.vel)) 45 | 46 | for i in range(10): 47 | thorns_ball.move(duration=0.05) 48 | logging.debug('=========================== after move {} ============================='.format(i)) 49 | logging.debug('[thorns] position={}, score={}, vel={}, move_frame={}' 50 | .format(thorns_ball.position, thorns_ball.score, thorns_ball.vel, thorns_ball.move_frame)) 51 | 52 | assert True 53 | 54 | def test_judge_in_rectangle(self): 55 | border = Border(0, 0, 800, 800) 56 | position = Vector2(400, 400) 57 | ball_id = uuid.uuid1() 58 | thorns_ball = ThornsBall(ball_id, position, border=border, score=10) 59 | rectangle = [300, 300, 500, 500] 60 | assert thorns_ball.judge_in_rectangle(rectangle) 61 | 62 | def test_eat_others(self): 63 | border = Border(0, 0, 800, 800) 64 | position = Vector2(400, 400) 65 | ball_id = uuid.uuid1() 66 | thorns_ball = ThornsBall(ball_id, position, border=border, score=10) 67 | position = Vector2(10, 10) 68 | ball_id = uuid.uuid1() 69 | base_ball = BaseBall(ball_id, position, border=border, score=1) 70 | thorns_ball.eat(base_ball) 71 | 72 | ball_id = uuid.uuid1() 73 | spore_position = Vector2(100, 100) 74 | spore_score = SporeBall.default_config().score_init 75 | direction = Vector2(1, 0) 76 | spore_ball = SporeBall(ball_id, spore_position, border=border, score=spore_score, direction=direction) 77 | thorns_ball.set_score(thorns_ball.score_max) 78 | thorns_ball.eat(spore_ball) 79 | -------------------------------------------------------------------------------- /gobigger/balls/thorns_ball.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from easydict import EasyDict 3 | import math 4 | from pygame.math import Vector2 5 | 6 | from gobigger.utils import format_vector, add_score, Border, deep_merge_dicts 7 | from .base_ball import BaseBall 8 | from .spore_ball import SporeBall 9 | 10 | 11 | class ThornsBall(BaseBall): 12 | """ 13 | Overview: 14 | - characteristic: 15 | * Can't move actively 16 | * Can eat spores. When eating spores, it will inherit the momentum of the spores and move a certain distance. 17 | * Can only be eaten by balls heavier than him. After eating, it will split the host into multiple smaller units. 18 | * Nothing happens when a ball lighter than him passes by 19 | """ 20 | @staticmethod 21 | def default_config(): 22 | cfg = BaseBall.default_config() 23 | cfg.update(dict( 24 | score_min=3, # Minimum score, greater than the player's maximum number of clones multiplied by the player's minimum weight 25 | score_max=5, # Maximum score 26 | eat_spore_vel_init=4, # Initialization speed after eating spores 27 | eat_spore_vel_zero_frame=10, # Time to zero speed after eating spores(s) 28 | )) 29 | return EasyDict(cfg) 30 | 31 | def __init__(self, ball_id, position, score, border, **kwargs): 32 | # init other kwargs 33 | kwargs = EasyDict(kwargs) 34 | cfg = ThornsBall.default_config() 35 | cfg = deep_merge_dicts(cfg, kwargs) 36 | super(ThornsBall, self).__init__(ball_id, position, score=score, border=border, **cfg) 37 | self.score_min = cfg.score_min 38 | self.score_max = cfg.score_max 39 | self.eat_spore_vel_init = cfg.eat_spore_vel_init 40 | self.eat_spore_vel_zero_frame = cfg.eat_spore_vel_zero_frame 41 | self.move_frame = 0 42 | self.vel = Vector2(0, 0) 43 | self.vel_piece = Vector2(0, 0) 44 | self.moving = False 45 | self.check_border() 46 | 47 | def move(self, direction=None, duration=0.05, **kwargs): 48 | assert duration > 0 49 | if self.moving: 50 | self.position = self.position + self.vel * duration 51 | self.move_frame += 1 52 | if self.move_frame < self.eat_spore_vel_zero_frame: 53 | self.vel = self.vel - self.vel_piece 54 | else: 55 | self.vel = Vector2(0, 0) 56 | self.vel_piece = Vector2(0, 0) 57 | self.moving = False 58 | self.check_border() 59 | return True 60 | 61 | def eat(self, ball): 62 | if isinstance(ball, SporeBall): 63 | self.set_score(add_score(self.score, ball.score)) 64 | if ball.vel.length() > 0: 65 | self.vel = self.eat_spore_vel_init * ball.vel.normalize() 66 | self.vel_piece = self.vel / self.eat_spore_vel_zero_frame 67 | self.move_time = 0 68 | self.moving = True 69 | else: 70 | logging.debug('ThornsBall can not eat {}'.format(type(ball))) 71 | return True 72 | 73 | def set_score(self, score: float) -> None: 74 | self.score = score 75 | if self.score > self.score_max: 76 | self.score = self.score_max 77 | elif self.score < self.score_min: 78 | self.score = self.score_min 79 | self.radius = self.score_to_radius(self.score) 80 | 81 | def save(self): 82 | return [self.position.x, self.position.y, self.radius] 83 | -------------------------------------------------------------------------------- /gobigger/bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opendilab/GoBigger/630a7032976fc2c6ecbf87db2c77bb9665e219d4/gobigger/bin/__init__.py -------------------------------------------------------------------------------- /gobigger/bin/demo_bot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import time 6 | import numpy as np 7 | import cv2 8 | import pygame 9 | import pickle 10 | 11 | from gobigger.agents import BotAgent 12 | from gobigger.utils import Border 13 | from gobigger.server import Server 14 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 15 | from gobigger.envs import GoBiggerEnv, create_env 16 | 17 | logging.basicConfig(level=logging.DEBUG) 18 | 19 | 20 | def demo_bot(): 21 | env = GoBiggerEnv(dict( 22 | team_num=4, 23 | player_num_per_team=3, 24 | frame_limit=60*20*1, 25 | playback_settings=dict( 26 | save_video=True, 27 | save_all=True, 28 | save_partial=True, 29 | ), 30 | )) 31 | obs = env.reset() 32 | bot_agents = [] 33 | team_infos = env.get_team_infos() 34 | for team_id, player_ids in team_infos: 35 | for player_id in player_ids: 36 | bot_agents.append(BotAgent(player_id, level=2)) 37 | time_step_all = 0 38 | for i in range(100000): 39 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 40 | t1 = time.time() 41 | obs, reward, done, info = env.step(actions=actions) 42 | t2 = time.time() 43 | time_step_all += t2-t1 44 | logging.debug('{} {:.4f} envstep {:.3f} / {:.3f}, leaderboard={}'\ 45 | .format(i, obs[0]['last_frame_count'], t2-t1, time_step_all/(i+1), obs[0]['leaderboard'])) 46 | if done: 47 | logging.debug('Game Over') 48 | break 49 | env.close() 50 | 51 | 52 | def demo_bot_st_t2p2(): 53 | env = create_env('st_t3p2', dict( 54 | playback_settings=dict( 55 | playback_type='by_frame', 56 | by_frame=dict( 57 | save_frame=True, 58 | ), 59 | ), 60 | ), step_mul=10) 61 | obs = env.reset() 62 | bot_agents = [] 63 | team_infos = env.get_team_infos() 64 | print(team_infos) 65 | for team_id, player_ids in team_infos: 66 | for player_id in player_ids: 67 | bot_agents.append(BotAgent(player_id, level=2)) 68 | time_step_all = 0 69 | for i in range(100000): 70 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 71 | t1 = time.time() 72 | obs, reward, done, info = env.step(actions=actions) 73 | t2 = time.time() 74 | time_step_all += t2-t1 75 | logging.debug('{} {:.4f} envstep {:.3f} / {:.3f}, leaderboard={}'\ 76 | .format(i, obs[0]['last_frame_count'], t2-t1, time_step_all/(i+1), obs[0]['leaderboard'])) 77 | if done: 78 | logging.debug('Game Over') 79 | break 80 | env.close() 81 | 82 | if __name__ == '__main__': 83 | # demo_bot() 84 | demo_bot_st_t2p2() 85 | -------------------------------------------------------------------------------- /gobigger/bin/play_hyper.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import pygame 6 | import numpy as np 7 | import cv2 8 | import argparse 9 | import time 10 | import importlib 11 | 12 | from gobigger.utils import Border 13 | from gobigger.server import Server 14 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 15 | from gobigger.agents import BotAgent 16 | 17 | logging.basicConfig(level=logging.DEBUG) 18 | 19 | 20 | def play_by_config(config_name): 21 | config_module = importlib.import_module('gobigger.hyper.configs.config_{}'.format(config_name)) 22 | config = config_module.server_default_config 23 | server = Server(config) 24 | server.reset() 25 | render = RealtimeRender(server.map_width, server.map_height) 26 | server.set_render(render) 27 | human_team_name = '0' 28 | human_team_player_name = [] 29 | bot_agents = [] 30 | for player in server.player_manager.get_players(): 31 | if player.team_name != human_team_name: 32 | bot_agents.append(BotAgent(player.name)) 33 | else: 34 | human_team_player_name.append(player.name) 35 | fps_real = 0 36 | t1 = time.time() 37 | clock = pygame.time.Clock() 38 | fps_set = server.state_tick_per_second 39 | for i in range(100000): 40 | obs = server.obs() 41 | # actions_bot = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 42 | actions_bot = {bot_agent.name: [None, None, -1] for bot_agent in bot_agents} 43 | actions = {player_name: [None, None, -1] for player_name in human_team_player_name} 44 | x, y = None, None 45 | action_type = -1 46 | # ================ control by keyboard =============== 47 | for event in pygame.event.get(): 48 | if event.type == pygame.KEYDOWN: 49 | x1, y1, x2, y2 = None, None, None, None 50 | action_type1 = -1 51 | action_type2 = -1 52 | if event.key == pygame.K_UP: 53 | x1, y1 = 0, -1 54 | if event.key == pygame.K_DOWN: 55 | x1, y1 = 0, 1 56 | if event.key == pygame.K_LEFT: 57 | x1, y1 = -1, 0 58 | if event.key == pygame.K_RIGHT: 59 | x1, y1 = 1, 0 60 | if event.key == pygame.K_LEFTBRACKET: # Spores 61 | action_type1 = 0 62 | if event.key == pygame.K_RIGHTBRACKET: # Splite 63 | action_type1 = 1 64 | if event.key == pygame.K_BACKSLASH: # Stop moving 65 | action_type1 = 2 66 | if event.key == pygame.K_w: 67 | x2, y2 = 0, -1 68 | if event.key == pygame.K_s: 69 | x2, y2 = 0, 1 70 | if event.key == pygame.K_a: 71 | x2, y2 = -1, 0 72 | if event.key == pygame.K_d: 73 | x2, y2 = 1, 0 74 | if event.key == pygame.K_1: # Spores 75 | action_type2 = 0 76 | if event.key == pygame.K_2: # Splite 77 | action_type2 = 1 78 | if event.key == pygame.K_3: # Stop moving 79 | action_type2 = 2 80 | actions = { 81 | human_team_player_name[0]: [x1, y1, action_type1], 82 | human_team_player_name[1]: [x2, y2, action_type2], 83 | } 84 | if server.last_time < server.match_time: 85 | actions.update(actions_bot) 86 | print(actions) 87 | server.step_state_tick(actions=actions) 88 | if actions is not None and x is not None and y is not None: 89 | render.fill(server, direction=Vector2(x, y), fps=fps_real, last_time=server.last_time) 90 | else: 91 | render.fill(server, direction=None, fps=fps_real, last_time=server.last_time) 92 | render.show() 93 | if i % server.state_tick_per_second == 0: 94 | t2 = time.time() 95 | fps_real = server.state_tick_per_second/(t2-t1) 96 | t1 = time.time() 97 | else: 98 | logging.debug('Game Over') 99 | break 100 | clock.tick(fps_set) 101 | render.close() 102 | 103 | 104 | if __name__ == '__main__': 105 | parser = argparse.ArgumentParser() 106 | parser.add_argument('-c', '--config', type=str, default='2f2s') 107 | args = parser.parse_args() 108 | 109 | play_by_config(args.config) 110 | -------------------------------------------------------------------------------- /gobigger/bin/profile.py: -------------------------------------------------------------------------------- 1 | import time 2 | from pygame.math import Vector2 3 | import numpy as np 4 | import numexpr as ne 5 | import random 6 | 7 | from pygame.math import Vector2 8 | 9 | from gobigger.agents import BotAgent 10 | from gobigger.utils import Border 11 | from gobigger.server import Server 12 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 13 | 14 | 15 | def method1(food_balls, cx, cy, r): 16 | food_count = 0 17 | food = len(food_balls) * [3 * [None]] 18 | food_radius = 2 19 | for ball in food_balls: 20 | x = ball.x 21 | y = ball.y 22 | if (x-cx)**2 + (y-cy)**2 < r**2: 23 | food[food_count] = [x, y, food_radius] 24 | food_count += 1 25 | return food[:food_count] 26 | 27 | 28 | def method2(food_balls, cx, cy, r): 29 | t1 = time.time() 30 | X = np.array([ball.x for ball in food_balls]) 31 | Y = np.array([ball.y for ball in food_balls]) 32 | t2 = time.time() 33 | res = ne.evaluate('((X-cx)**2 + (Y-cy)**2) eat_ratio * size_B 11 | playback_settings=dict( 12 | playback_type='none', # ['', 'by_video', 'by_frame', 'by_action'] 13 | by_video=dict( 14 | save_video=False, 15 | save_fps=10, 16 | save_resolution=552, 17 | save_all=True, 18 | save_partial=False, 19 | save_dir='.', 20 | save_name_prefix='test', 21 | ), 22 | by_frame=dict( 23 | save_frame=False, 24 | save_all=True, 25 | save_partial=False, 26 | save_dir='.', 27 | save_name_prefix='test', 28 | ), 29 | by_action=dict( 30 | save_action=False, 31 | save_dir='.', 32 | save_name_prefix='test', 33 | ), 34 | ), 35 | opening_settings=dict( 36 | opening_type='none', # ['none', 'handcraft', 'from_frame'] 37 | handcraft=dict( 38 | food=[], # only position and radius 39 | thorns=[], # only position and radius 40 | spore=[], # only position and radius 41 | clone=[], # only position and radius and player and team 42 | ), 43 | from_frame=dict( 44 | frame_path='', 45 | ), 46 | ), 47 | manager_settings=dict( 48 | # food setting 49 | food_manager=dict( 50 | num_init=800, # initial number 51 | num_min=800, # Minimum number 52 | num_max=900, # Maximum number 53 | refresh_frame_freq=8, # Time interval (seconds) for refreshing food in the map 54 | refresh_percent=0.01, # The number of refreshed foods in the map each time 55 | ball_settings=dict( # The specific parameter description can be viewed in the ball module 56 | score_min=100, # radius=0.438, # score = 100 57 | score_max=100, 58 | ), 59 | ), 60 | # thorns setting 61 | thorns_manager=dict( 62 | num_init=9, # initial number 63 | num_min=9, # Minimum number 64 | num_max=12, # Maximum number 65 | refresh_frame_freq=120, # Time interval (seconds) for refreshing thorns in the map 66 | refresh_percent=0.2, # The number of refreshed thorns in the map each time 67 | ball_settings=dict( # The specific parameter description can be viewed in the ball module 68 | score_min=10000, # 2.086 69 | score_max=15000, # 2.540 70 | eat_spore_vel_init=10, 71 | eat_spore_vel_zero_frame=20, 72 | ), 73 | ), 74 | # player setting 75 | player_manager=dict( 76 | ball_settings=dict( # The specific parameter description can be viewed in the ball module 77 | acc_weight=30, 78 | vel_max=50, 79 | score_init=1000, # 0.755, 80 | score_respawn=1000, # score after respawn, usually the same with score_init 81 | part_num_max=16, 82 | on_thorns_part_num=10, 83 | on_thorns_part_score_max=5000, 84 | split_score_min=3600, # radius = 1.289 85 | eject_score_min=3200, # radius = 1.222 86 | recombine_frame=400, 87 | split_vel_zero_frame=14, 88 | score_decay_min=2600, 89 | score_decay_rate_per_frame=0.00005, # * sqrt(radius) 90 | center_acc_weight=10, 91 | ), 92 | ), 93 | # spore setting 94 | spore_manager=dict( 95 | ball_settings=dict( # The specific parameter description can be viewed in the ball module 96 | score_init=1400, # 0.859 97 | vel_init=30, 98 | vel_zero_frame=20, 99 | ), 100 | ) 101 | ), 102 | obs_settings=dict( 103 | obs_type='partial', # ['partial', 'all'] 104 | partial=dict( 105 | type='square', # ['circle', 'square'] 106 | vision_x_min=10, 107 | vision_y_min=10, 108 | scale_up_ratio=1.8, 109 | ) 110 | ), 111 | ) 112 | -------------------------------------------------------------------------------- /gobigger/configs/server_sp_default_config.py: -------------------------------------------------------------------------------- 1 | from .server_default_config import server_default_config 2 | 3 | server_sp_default_config = server_default_config 4 | -------------------------------------------------------------------------------- /gobigger/configs/sp_t4p3.py: -------------------------------------------------------------------------------- 1 | from .server_sp_default_config import server_sp_default_config 2 | 3 | sp_t4p3 = server_sp_default_config 4 | -------------------------------------------------------------------------------- /gobigger/configs/st_t1p1.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t1p1 = EasyDict(dict( 7 | team_num=1, 8 | player_num_per_team=1, 9 | map_width=32, 10 | map_height=32, 11 | frame_limit=60*3*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=65, 15 | num_min=65, 16 | num_max=75, 17 | ), 18 | thorns_manager=dict( 19 | num_init=1, 20 | num_min=1, 21 | num_max=2, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t1p1 = deep_merge_dicts(cfg_ori, st_t1p1) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t1p2.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t1p2 = EasyDict(dict( 7 | team_num=1, 8 | player_num_per_team=2, 9 | map_width=48, 10 | map_height=48, 11 | frame_limit=60*3*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=130, 15 | num_min=130, 16 | num_max=150, 17 | ), 18 | thorns_manager=dict( 19 | num_init=2, 20 | num_min=2, 21 | num_max=3, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t1p2 = deep_merge_dicts(cfg_ori, st_t1p2) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t2p1.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t2p1 = EasyDict(dict( 7 | team_num=2, 8 | player_num_per_team=1, 9 | map_width=48, 10 | map_height=48, 11 | frame_limit=60*3*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=130, 15 | num_min=130, 16 | num_max=150, 17 | ), 18 | thorns_manager=dict( 19 | num_init=2, 20 | num_min=2, 21 | num_max=3, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t2p1 = deep_merge_dicts(cfg_ori, st_t2p1) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t2p2.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t2p2 = EasyDict(dict( 7 | team_num=2, 8 | player_num_per_team=2, 9 | map_width=64, 10 | map_height=64, 11 | frame_limit=60*3*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=260, 15 | num_min=260, 16 | num_max=300, 17 | ), 18 | thorns_manager=dict( 19 | num_init=3, 20 | num_min=3, 21 | num_max=4, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=13000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t2p2 = deep_merge_dicts(cfg_ori, st_t2p2) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t3p2.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t3p2 = EasyDict(dict( 7 | team_num=3, 8 | player_num_per_team=2, 9 | map_width=88, 10 | map_height=88, 11 | frame_limit=60*3*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=500, 15 | num_min=500, 16 | num_max=560, 17 | ), 18 | thorns_manager=dict( 19 | num_init=5, 20 | num_min=5, 21 | num_max=6, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=13000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t3p2 = deep_merge_dicts(cfg_ori, st_t3p2) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t4p3.py: -------------------------------------------------------------------------------- 1 | from .server_default_config import server_default_config 2 | 3 | st_t4p3 = server_default_config 4 | -------------------------------------------------------------------------------- /gobigger/configs/st_t5p3.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t5p3 = EasyDict(dict( 7 | team_num=5, 8 | player_num_per_team=3, 9 | map_width=128, 10 | map_height=128, 11 | frame_limit=60*12*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=900, 15 | num_min=900, 16 | num_max=1000, 17 | ), 18 | thorns_manager=dict( 19 | num_init=10, 20 | num_min=12, 21 | num_max=12, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t5p3 = deep_merge_dicts(cfg_ori, st_t5p3) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t5p4.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t5p4 = EasyDict(dict( 7 | team_num=5, 8 | player_num_per_team=4, 9 | map_width=144, 10 | map_height=144, 11 | frame_limit=60*12*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=900, 15 | num_min=900, 16 | num_max=1000, 17 | ), 18 | thorns_manager=dict( 19 | num_init=10, 20 | num_min=12, 21 | num_max=12, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t5p4 = deep_merge_dicts(cfg_ori, st_t5p4) 31 | -------------------------------------------------------------------------------- /gobigger/configs/st_t6p4.py: -------------------------------------------------------------------------------- 1 | from easydict import EasyDict 2 | from gobigger.utils import deep_merge_dicts 3 | from .server_default_config import server_default_config 4 | 5 | cfg_ori = EasyDict(server_default_config) 6 | st_t6p4 = EasyDict(dict( 7 | team_num=6, 8 | player_num_per_team=4, 9 | map_width=144, 10 | map_height=144, 11 | frame_limit=60*12*20, 12 | manager_settings=dict( 13 | food_manager=dict( 14 | num_init=1000, 15 | num_min=1000, 16 | num_max=1100, 17 | ), 18 | thorns_manager=dict( 19 | num_init=11, 20 | num_min=13, 21 | num_max=13, 22 | ), 23 | player_manager=dict( 24 | ball_settings=dict( 25 | score_init=1000, 26 | ), 27 | ), 28 | ), 29 | )) 30 | st_t6p4 = deep_merge_dicts(cfg_ori, st_t6p4) 31 | -------------------------------------------------------------------------------- /gobigger/envs/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from .gobigger_env import GoBiggerEnv 4 | from .gobigger_sp_env import GoBiggerSPEnv 5 | from gobigger.configs import * 6 | from gobigger.utils import deep_merge_dicts 7 | 8 | def create_env_st(cfg, **kwargs): 9 | return GoBiggerEnv(cfg, **kwargs) 10 | 11 | def create_env_sp(cfg, **kwargs): 12 | return GoBiggerSPEnv(cfg, **kwargs) 13 | 14 | def create_env(env_name, custom_cfg={}, **kwargs): 15 | ''' 16 | env_name choice in ['st_v0', 'sp_v0'] 17 | ''' 18 | cfg = importlib.import_module('gobigger.configs.{}'.format(env_name)) 19 | cfg = eval('cfg.{}'.format(env_name)) 20 | cfg = deep_merge_dicts(cfg, custom_cfg) 21 | if env_name.startswith('st'): 22 | return create_env_st(cfg, **kwargs) 23 | elif env_name.startswith('sp'): 24 | return create_env_sp(cfg, **kwargs) 25 | else: 26 | raise NotImplementedError 27 | 28 | def create_env_custom(type, cfg=None, **kwargs): 29 | if type == 'st': 30 | return create_env_st(cfg, **kwargs) 31 | elif type == 'sp': 32 | return create_env_sp(cfg, **kwargs) 33 | else: 34 | raise NotImplementedError 35 | -------------------------------------------------------------------------------- /gobigger/envs/gobigger_env.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import gym 4 | import time 5 | 6 | from gobigger.server import Server 7 | from gobigger.render import EnvRender 8 | import copy 9 | 10 | 11 | class GoBiggerEnv(gym.Env): 12 | 13 | def __init__(self, server_cfg=None, step_mul=2, **kwargs): 14 | self.server_cfg = server_cfg 15 | self.step_mul = step_mul 16 | self.init_server() 17 | 18 | def step(self, actions): 19 | for i in range(self.step_mul): 20 | if i==0: 21 | done = self.server.step(actions=actions) 22 | else: 23 | done = self.server.step(actions=None) 24 | obs_raw = self.server.obs() 25 | global_state, player_states, info = obs_raw 26 | obs = [global_state, player_states] 27 | total_score = [global_state['leaderboard'][i] \ 28 | for i in range(len(global_state['leaderboard']))] 29 | assert len(self.last_total_score) == len(total_score) 30 | reward = [total_score[i] - self.last_total_score[i] for i in range(len(total_score))] 31 | self.last_total_score = total_score 32 | return obs, reward, done, info 33 | 34 | def reset(self): 35 | self.server.reset() 36 | obs_raw = self.server.obs() 37 | global_state, player_states, info = obs_raw 38 | obs = [global_state, player_states] 39 | self.last_total_score = [global_state['leaderboard'][i] \ 40 | for i in range(len(global_state['leaderboard']))] 41 | return obs 42 | 43 | def close(self): 44 | self.server.close() 45 | 46 | def seed(self, seed): 47 | self.server.seed(seed) 48 | 49 | def get_team_infos(self): 50 | assert hasattr(self, 'server'), "Please call `reset()` first" 51 | return self.server.get_team_infos() 52 | 53 | def init_server(self): 54 | self.server = Server(cfg=self.server_cfg) 55 | -------------------------------------------------------------------------------- /gobigger/envs/gobigger_sp_env.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import gym 4 | import time 5 | 6 | from gobigger.server import ServerSP 7 | from gobigger.render import EnvRender 8 | from .gobigger_env import GoBiggerEnv 9 | 10 | 11 | class GoBiggerSPEnv(GoBiggerEnv): 12 | 13 | def init_server(self): 14 | self.server = ServerSP(cfg=self.server_cfg) 15 | -------------------------------------------------------------------------------- /gobigger/envs/tests/test_gobigger_env.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.envs import GoBiggerEnv 7 | 8 | logging.basicConfig(level=logging.DEBUG) 9 | 10 | 11 | @pytest.mark.unittest 12 | class TestGoBiggerEnv: 13 | 14 | def test_env(self): 15 | env = GoBiggerEnv() 16 | obs = env.reset() 17 | env.seed(1000) 18 | obs, reward, done, info = env.step(actions=None) 19 | global_state, player_states = obs 20 | assert len(player_states) == env.server.team_num * env.server.player_num_per_team 21 | env.close() 22 | assert True 23 | -------------------------------------------------------------------------------- /gobigger/envs/tests/test_gobigger_sp_env.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.envs import GoBiggerSPEnv 7 | 8 | logging.basicConfig(level=logging.DEBUG) 9 | 10 | 11 | @pytest.mark.unittest 12 | class TestGoBiggerEnv: 13 | 14 | def test_env(self): 15 | env = GoBiggerSPEnv() 16 | obs = env.reset() 17 | env.seed(1000) 18 | obs, reward, done, info = env.step(actions=None) 19 | global_state, player_states = obs 20 | assert len(player_states) == env.server.team_num * env.server.player_num_per_team 21 | env.close() 22 | assert True 23 | -------------------------------------------------------------------------------- /gobigger/managers/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_manager import BaseManager 2 | from .food_manager import FoodManager 3 | from .spore_manager import SporeManager 4 | from .thorns_manager import ThornsManager 5 | from .player_manager import PlayerManager 6 | from .player_sp_manager import PlayerSPManager 7 | -------------------------------------------------------------------------------- /gobigger/managers/base_manager.py: -------------------------------------------------------------------------------- 1 | import math 2 | import logging 3 | from abc import ABC, abstractmethod 4 | from easydict import EasyDict 5 | from pygame.math import Vector2 6 | 7 | from gobigger.utils import format_vector, Border 8 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 9 | 10 | 11 | class BaseManager(ABC): 12 | ''' 13 | Overview: 14 | Base class for all ball managers 15 | ''' 16 | def __init__(self, cfg, border): 17 | self.cfg = cfg 18 | self.border = border 19 | self.balls = {} 20 | self.ball_settings = self.cfg.ball_settings 21 | 22 | def get_balls(self): 23 | ''' 24 | Overview: 25 | Get all balls currently managed 26 | ''' 27 | return self.balls.values() 28 | 29 | def add_balls(self, balls): 30 | ''' 31 | Overview: 32 | Add one (or more) balls 33 | ''' 34 | raise NotImplementedError 35 | 36 | def refresh(self): 37 | ''' 38 | Overview: 39 | Refresh. Used to refresh the balls in management. Such as replenishing eaten food balls 40 | ''' 41 | raise NotImplementedError 42 | 43 | def remove_balls(self, balls): 44 | ''' 45 | Overview: 46 | Remove managed balls 47 | ''' 48 | raise NotImplementedError 49 | 50 | def spawn_ball(self): 51 | raise NotImplementedError 52 | 53 | def init_balls(self): 54 | raise NotImplementedError 55 | 56 | def step(self, duration): 57 | ''' 58 | Overview: 59 | Perform a status update under the control of the server 60 | ''' 61 | raise NotImplementedError 62 | 63 | def obs(self): 64 | ''' 65 | Overview: 66 | Return data available for observation 67 | ''' 68 | raise NotImplementedError 69 | 70 | def reset(self): 71 | raise NotImplementedError 72 | -------------------------------------------------------------------------------- /gobigger/managers/food_manager.py: -------------------------------------------------------------------------------- 1 | import math 2 | import logging 3 | import random 4 | import uuid 5 | import numpy as np 6 | from abc import ABC, abstractmethod 7 | from easydict import EasyDict 8 | from pygame.math import Vector2 9 | 10 | from .base_manager import BaseManager 11 | from gobigger.utils import format_vector, Border, SequenceGenerator 12 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 13 | 14 | 15 | class FoodManager(BaseManager): 16 | 17 | def __init__(self, cfg, border, random_generator=None, sequence_generator=None): 18 | super(FoodManager, self).__init__(cfg, border) 19 | self.refresh_frame_freq = self.cfg.refresh_frame_freq 20 | self.refresh_frame_count = 0 21 | if random_generator is not None: 22 | self._random = random_generator 23 | else: 24 | self._random = random.Random() 25 | if sequence_generator is not None: 26 | self.sequence_generator = sequence_generator 27 | else: 28 | self.sequence_generator = SequenceGenerator() 29 | 30 | def get_balls(self): 31 | return list(self.balls.values()) 32 | 33 | def add_balls(self, balls): 34 | if isinstance(balls, list): 35 | for ball in balls: 36 | self.balls[ball.ball_id] = ball 37 | elif isinstance(balls, FoodBall): 38 | self.balls[balls.ball_id] = balls 39 | return True 40 | 41 | def refresh(self): 42 | left_num = self.cfg.num_max - len(self.balls) 43 | todo_num = min(math.ceil(self.cfg.refresh_percent * left_num), left_num) 44 | new_balls = {} 45 | for _ in range(todo_num): 46 | ball = self.spawn_ball() 47 | self.add_balls(ball) 48 | new_balls[ball.ball_id] = ball.save() 49 | return new_balls 50 | 51 | def remove_balls(self, balls): 52 | if isinstance(balls, list): 53 | for ball in balls: 54 | ball.remove() 55 | try: 56 | del self.balls[ball.ball_id] 57 | except: 58 | pass 59 | elif isinstance(balls, FoodBall): 60 | balls.remove() 61 | try: 62 | del self.balls[balls.ball_id] 63 | except: 64 | pass 65 | 66 | def spawn_ball(self, position=None, score=None): 67 | if position is None: 68 | position = self.border.sample() 69 | if score is None: 70 | score = self._random.uniform(self.ball_settings.score_min, self.ball_settings.score_max) 71 | ball_id = self.sequence_generator.get() 72 | return FoodBall(ball_id=ball_id, position=position, border=self.border, score=score, **self.ball_settings) 73 | 74 | def init_balls(self, custom_init=None): 75 | if custom_init is None or len(custom_init) == 0: 76 | for _ in range(self.cfg.num_init): 77 | ball = self.spawn_ball() 78 | self.balls[ball.ball_id] = ball 79 | else: 80 | for ball_cfg in custom_init: 81 | ball = self.spawn_ball(position=Vector2(*ball_cfg[:2]), score=ball_cfg[2]) 82 | self.balls[ball.ball_id] = ball 83 | 84 | def step(self, duration): 85 | self.refresh_frame_count += 1 86 | new_balls = {} 87 | if self.refresh_frame_count >= self.refresh_frame_freq: 88 | new_balls = self.refresh() 89 | self.refresh_frame_count = 0 90 | return new_balls 91 | 92 | def reset(self): 93 | self.refresh_frame_count = 0 94 | self.balls = {} 95 | return True 96 | -------------------------------------------------------------------------------- /gobigger/managers/player_sp_manager.py: -------------------------------------------------------------------------------- 1 | from gobigger.utils import SequenceGenerator 2 | from gobigger.players import HumanSPPlayer 3 | from .player_manager import PlayerManager 4 | 5 | 6 | class PlayerSPManager(PlayerManager): 7 | 8 | def __init__(self, cfg, border, team_num, player_num_per_team, spore_manager_settings, 9 | random_generator=None, sequence_generator=None): 10 | super(PlayerSPManager, self).__init__(cfg, border, team_num, player_num_per_team, spore_manager_settings, 11 | random_generator=random_generator) 12 | if sequence_generator is not None: 13 | self.sequence_generator = sequence_generator 14 | else: 15 | self.sequence_generator = SequenceGenerator() 16 | 17 | def init_balls(self, custom_init=None): 18 | if custom_init is None or len(custom_init) == 0: 19 | for i in range(self.team_num): 20 | team_id = i 21 | for j in range(self.player_num_per_team): 22 | player_id = i * self.player_num_per_team + j 23 | player = HumanSPPlayer(cfg=self.cfg.ball_settings, team_id=team_id, player_id=player_id, 24 | border=self.border, spore_settings=self.spore_settings, 25 | sequence_generator=self.sequence_generator) 26 | player.respawn(position=self.border.sample()) 27 | self.players[player_id] = player 28 | else: 29 | raise NotImplementedError 30 | -------------------------------------------------------------------------------- /gobigger/managers/spore_manager.py: -------------------------------------------------------------------------------- 1 | import math 2 | import logging 3 | import random 4 | import uuid 5 | from abc import ABC, abstractmethod 6 | from easydict import EasyDict 7 | from pygame.math import Vector2 8 | 9 | from .base_manager import BaseManager 10 | from gobigger.utils import format_vector, Border, SequenceGenerator 11 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 12 | 13 | 14 | class SporeManager(BaseManager): 15 | 16 | def __init__(self, cfg, border, random_generator=None, sequence_generator=None): 17 | super(SporeManager, self).__init__(cfg, border) 18 | if random_generator is not None: 19 | self._random = random_generator 20 | else: 21 | self._random = random.Random() 22 | if sequence_generator is not None: 23 | self.sequence_generator = sequence_generator 24 | else: 25 | self.sequence_generator = SequenceGenerator() 26 | 27 | def get_balls(self): 28 | return list(self.balls.values()) 29 | 30 | def add_balls(self, balls): 31 | if isinstance(balls, list): 32 | for ball in balls: 33 | self.balls[ball.ball_id] = ball 34 | elif isinstance(balls, SporeBall): 35 | self.balls[balls.ball_id] = balls 36 | return True 37 | 38 | def remove_balls(self, balls): 39 | if isinstance(balls, list): 40 | for ball in balls: 41 | ball.remove() 42 | try: 43 | del self.balls[ball.ball_id] 44 | except: 45 | pass 46 | elif isinstance(balls, SporeBall): 47 | balls.remove() 48 | try: 49 | del self.balls[balls.ball_id] 50 | except: 51 | pass 52 | 53 | def spawn_ball(self, position=None): 54 | if position is None: 55 | position = self.border.sample() 56 | name = uuid.uuid1() 57 | return SporeBall(name=name, position=position, border=self.border, score=self.ball_settings.score_init, 58 | direction=Vector2(1,0)) 59 | 60 | def init_balls(self, custom_init=None): 61 | # [position.x, position.y, score, direction.x, direction.y, vel.x, vel.y, acc.x, acc.y, 62 | # move_time, moving] 63 | if custom_init is not None: 64 | for ball_cfg in custom_init: 65 | ball = self.spawn_ball(position=Vector2(*ball_cfg[:2])) 66 | if len(ball_cfg) > 2: 67 | ball.direction = Vector2(*ball_cfg[2:4]) 68 | ball.vel = Vector2(*ball_cfg[4:6]) 69 | ball.move_frame = ball_cfg[6] 70 | ball.moving = ball_cfg[7] 71 | ball.owner = ball_cfg[8] 72 | self.balls[ball.name] = ball 73 | 74 | def step(self, duration): 75 | return 76 | 77 | def reset(self): 78 | self.balls = {} 79 | return True 80 | -------------------------------------------------------------------------------- /gobigger/managers/tests/test_base_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.managers import BaseManager 7 | from gobigger.utils import Border 8 | from gobigger.server import Server 9 | 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | @pytest.mark.unittest 13 | class TestBaseManager: 14 | 15 | def test_init(self): 16 | cfg = Server.default_config() 17 | border = Border(0, 0, 100, 100) 18 | base_manager = BaseManager(cfg=cfg.manager_settings.food_manager, border=border) 19 | assert True 20 | 21 | def test_others(self): 22 | cfg = Server.default_config() 23 | border = Border(0, 0, 100, 100) 24 | base_manager = BaseManager(cfg=cfg.manager_settings.food_manager, border=border) 25 | base_manager.get_balls() 26 | with pytest.raises(Exception) as e: 27 | base_manager.add_balls([]) 28 | with pytest.raises(Exception) as e: 29 | base_manager.refresh() 30 | with pytest.raises(Exception) as e: 31 | base_manager.remove_balls(balls=None) 32 | with pytest.raises(Exception) as e: 33 | base_manager.spawn_ball() 34 | with pytest.raises(Exception) as e: 35 | base_manager.init_balls() 36 | with pytest.raises(Exception) as e: 37 | base_manager.step(duration=None) 38 | with pytest.raises(Exception) as e: 39 | base_manager.obs() 40 | with pytest.raises(Exception) as e: 41 | base_manager.reset() 42 | 43 | 44 | -------------------------------------------------------------------------------- /gobigger/managers/tests/test_food_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.managers import FoodManager 7 | from gobigger.utils import Border 8 | from gobigger.server import Server 9 | 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | @pytest.mark.unittest 13 | class TestFoodManager: 14 | 15 | def get_manager(self): 16 | cfg = Server.default_config() 17 | border = Border(0, 0, cfg.map_width, cfg.map_height) 18 | food_manager = FoodManager(cfg=cfg.manager_settings.food_manager, border=border) 19 | return food_manager 20 | 21 | def test_init(self): 22 | food_manager = self.get_manager() 23 | assert True 24 | 25 | def test_get_balls(self): 26 | food_manager = self.get_manager() 27 | food_manager.init_balls() 28 | balls = food_manager.get_balls() 29 | assert len(balls) == food_manager.cfg.num_init 30 | for i in range(10): 31 | logging.debug(balls[i]) 32 | assert True 33 | 34 | def test_remove_balls(self): 35 | food_manager = self.get_manager() 36 | food_manager.init_balls() 37 | balls = food_manager.get_balls() 38 | assert len(balls) == food_manager.cfg.num_init 39 | food_manager.remove_balls(balls[:100]) 40 | logging.debug('[FoodManager.remove_balls] init num: {}, now num {}' 41 | .format(food_manager.cfg.num_init, len(food_manager.get_balls()))) 42 | assert True 43 | 44 | def test_step(self): 45 | food_manager = self.get_manager() 46 | food_manager.init_balls() 47 | balls = food_manager.get_balls() 48 | assert len(balls) == food_manager.cfg.num_init 49 | food_manager.remove_balls(balls[:100]) 50 | logging.debug('[FoodManager.remove_balls] init num: {}, now num {}' 51 | .format(food_manager.cfg.num_init, len(food_manager.get_balls()))) 52 | refresh_frame_freq = food_manager.cfg.refresh_frame_freq 53 | logging.debug('=================== test step ===================') 54 | for i in range(10): 55 | food_manager.step(duration=None) 56 | logging.debug('[FoodManager.step] {} food num = {}'.format(i, len(food_manager.get_balls()))) 57 | 58 | def test_reset(self): 59 | food_manager = self.get_manager() 60 | food_manager.init_balls() 61 | balls = food_manager.get_balls() 62 | assert len(balls) == food_manager.cfg.num_init 63 | food_manager.reset() 64 | balls = food_manager.get_balls() 65 | assert len(balls) == 0 66 | 67 | def test_add_balls(self): 68 | to_add_list = [] 69 | food_manager = self.get_manager() 70 | for _ in range(2): 71 | to_add_list.append(food_manager.spawn_ball()) 72 | assert food_manager.add_balls(to_add_list) 73 | 74 | def test_init_balls_custom(self): 75 | custom_init = [[100, 100, 2]] 76 | food_manager = self.get_manager() 77 | food_manager.init_balls(custom_init) 78 | -------------------------------------------------------------------------------- /gobigger/managers/tests/test_spore_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.managers import SporeManager 7 | from gobigger.balls import SporeBall 8 | from gobigger.utils import Border 9 | from gobigger.server import Server 10 | 11 | logging.basicConfig(level=logging.DEBUG) 12 | 13 | @pytest.mark.unittest 14 | class TestSporeManager: 15 | 16 | def get_manager(self): 17 | cfg = Server.default_config() 18 | border = Border(0, 0, cfg.map_width, cfg.map_height) 19 | spore_manager = SporeManager(cfg=cfg.manager_settings.spore_manager, border=border) 20 | return spore_manager 21 | 22 | def get_spore_ball(self): 23 | ball_id = uuid.uuid1() 24 | border = Border(0, 0, 1000, 1000) 25 | position = Vector2(100, 100) 26 | score = SporeBall.default_config().score_init 27 | direction = Vector2(1, 0) 28 | return SporeBall(ball_id, position, border=border, score=score, direction=direction) 29 | 30 | def test_init(self): 31 | spore_manager = self.get_manager() 32 | assert True 33 | 34 | def test_get_balls(self): 35 | spore_manager = self.get_manager() 36 | for i in range(10): 37 | spore_manager.add_balls(self.get_spore_ball()) 38 | spore_manager.add_balls([self.get_spore_ball(), self.get_spore_ball()]) 39 | balls = spore_manager.get_balls() 40 | for i in range(10): 41 | logging.debug(balls[i]) 42 | assert True 43 | 44 | def test_remove_balls(self): 45 | spore_manager = self.get_manager() 46 | for i in range(10): 47 | spore_manager.add_balls(self.get_spore_ball()) 48 | balls = spore_manager.get_balls() 49 | original_len = len(balls) 50 | spore_manager.remove_balls(balls[:5]) 51 | logging.debug('[SporeManager.remove_balls] init num: {}, now num {}' 52 | .format(original_len, len(spore_manager.get_balls()))) 53 | assert True 54 | 55 | def test_reset(self): 56 | spore_manager = self.get_manager() 57 | for i in range(10): 58 | spore_manager.add_balls(self.get_spore_ball()) 59 | balls = spore_manager.get_balls() 60 | spore_manager.reset() 61 | assert len(spore_manager.balls) == 0 62 | -------------------------------------------------------------------------------- /gobigger/managers/tests/test_thorns_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.managers import ThornsManager 7 | from gobigger.utils import Border 8 | from gobigger.server import Server 9 | 10 | logging.basicConfig(level=logging.DEBUG) 11 | 12 | @pytest.mark.unittest 13 | class TestThornsManager: 14 | 15 | def get_manager(self): 16 | cfg = Server.default_config() 17 | border = Border(0, 0, cfg.map_width, cfg.map_height) 18 | thorns_manager = ThornsManager(cfg=cfg.manager_settings.thorns_manager, border=border) 19 | return thorns_manager 20 | 21 | def test_init(self): 22 | thorns_manager = self.get_manager() 23 | assert True 24 | 25 | def test_get_balls(self): 26 | thorns_manager = self.get_manager() 27 | thorns_manager.init_balls() 28 | balls = thorns_manager.get_balls() 29 | assert len(balls) == thorns_manager.cfg.num_init 30 | for i in range(2): 31 | logging.debug(balls[i]) 32 | assert True 33 | 34 | def test_remove_balls(self): 35 | thorns_manager = self.get_manager() 36 | thorns_manager.init_balls() 37 | balls = thorns_manager.get_balls() 38 | assert len(balls) == thorns_manager.cfg.num_init 39 | thorns_manager.remove_balls(balls[:20]) 40 | logging.debug('[ThornsManager.remove_balls] init num: {}, now num {}' 41 | .format(thorns_manager.cfg.num_init, len(thorns_manager.get_balls()))) 42 | assert True 43 | 44 | def test_step(self): 45 | thorns_manager = self.get_manager() 46 | thorns_manager.init_balls() 47 | balls = thorns_manager.get_balls() 48 | assert len(balls) == thorns_manager.cfg.num_init 49 | thorns_manager.remove_balls(balls[:20]) 50 | logging.debug('[ThornsManager.remove_balls] init num: {}, now num {}' 51 | .format(thorns_manager.cfg.num_init, len(thorns_manager.get_balls()))) 52 | refresh_frame_freq = thorns_manager.cfg.refresh_frame_freq 53 | logging.debug('=================== test step ===================') 54 | for i in range(20): 55 | thorns_manager.step(duration=None) 56 | logging.debug('[FoodManager.step] {} food num = {}'.format(i, len(thorns_manager.get_balls()))) 57 | 58 | def test_reset(self): 59 | thorns_manager = self.get_manager() 60 | thorns_manager.init_balls() 61 | balls = thorns_manager.get_balls() 62 | assert len(balls) == thorns_manager.cfg.num_init 63 | thorns_manager.reset() 64 | assert len(thorns_manager.balls) == 0 65 | 66 | def test_add_remove_list(self): 67 | thorns_manager = self.get_manager() 68 | thorns_manager.init_balls() 69 | balls = thorns_manager.get_balls() 70 | thorns_manager.add_balls(balls) 71 | thorns_manager.remove_balls(balls) 72 | -------------------------------------------------------------------------------- /gobigger/managers/thorns_manager.py: -------------------------------------------------------------------------------- 1 | import math 2 | import logging 3 | import random 4 | import uuid 5 | from abc import ABC, abstractmethod 6 | from easydict import EasyDict 7 | from pygame.math import Vector2 8 | 9 | from .base_manager import BaseManager 10 | from gobigger.utils import format_vector, Border, SequenceGenerator 11 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 12 | 13 | 14 | class ThornsManager(BaseManager): 15 | 16 | def __init__(self, cfg, border, random_generator=None, sequence_generator=None): 17 | super(ThornsManager, self).__init__(cfg, border) 18 | self.refresh_frame_freq = self.cfg.refresh_frame_freq 19 | self.refresh_frame_count = 0 20 | if random_generator is not None: 21 | self._random = random_generator 22 | else: 23 | self._random = random.Random() 24 | if sequence_generator is not None: 25 | self.sequence_generator = sequence_generator 26 | else: 27 | self.sequence_generator = SequenceGenerator() 28 | 29 | def get_balls(self): 30 | return list(self.balls.values()) 31 | 32 | def add_balls(self, balls): 33 | if isinstance(balls, list): 34 | for ball in balls: 35 | self.balls[ball.ball_id] = ball 36 | elif isinstance(balls, ThornsBall): 37 | self.balls[balls.ball_id] = balls 38 | return True 39 | 40 | def refresh(self): 41 | left_num = self.cfg.num_max - len(self.balls) 42 | todo_num = min(math.ceil(self.cfg.refresh_percent * left_num), left_num) 43 | new_balls = {} 44 | for _ in range(todo_num): 45 | ball = self.spawn_ball() 46 | self.add_balls(ball) 47 | new_balls[ball.ball_id] = ball.save() 48 | return new_balls 49 | 50 | def remove_balls(self, balls): 51 | if isinstance(balls, list): 52 | for ball in balls: 53 | ball.remove() 54 | try: 55 | del self.balls[ball.ball_id] 56 | except: 57 | pass 58 | elif isinstance(balls, ThornsBall): 59 | balls.remove() 60 | try: 61 | del self.balls[balls.ball_id] 62 | except: 63 | pass 64 | 65 | def spawn_ball(self, position=None, score=None): 66 | if position is None: 67 | position = self.border.sample() 68 | if score is None: 69 | score = self._random.uniform(self.ball_settings.score_min, self.ball_settings.score_max) 70 | ball_id = self.sequence_generator.get() 71 | return ThornsBall(ball_id=ball_id, position=position, border=self.border, score=score, **self.ball_settings) 72 | 73 | def init_balls(self, custom_init=None): 74 | # [position.x, position.y, score, vel.x, vel.y, acc.x, acc.y, 75 | # move_time, moving] 76 | if custom_init is None or len(custom_init) == 0: 77 | for _ in range(self.cfg.num_init): 78 | ball = self.spawn_ball() 79 | self.balls[ball.ball_id] = ball 80 | else: 81 | for ball_cfg in custom_init: 82 | ball = self.spawn_ball(position=Vector2(*ball_cfg[:2]), score=ball_cfg[2]) 83 | if len(ball_cfg) > 3: 84 | ball.vel = Vector2(*ball_cfg[3:5]) 85 | ball.move_frame = Vector2(*ball_cfg[5]) 86 | ball.moving = ball_cfg[6] 87 | self.balls[ball.ball_id] = ball 88 | 89 | def step(self, duration): 90 | self.refresh_frame_count += 1 91 | new_balls = {} 92 | if self.refresh_frame_count > self.refresh_frame_freq: 93 | new_balls = self.refresh() 94 | self.refresh_frame_count = 0 95 | return new_balls 96 | 97 | def reset(self): 98 | self.refresh_frame_count = 0 99 | self.balls = {} 100 | return True 101 | -------------------------------------------------------------------------------- /gobigger/playbacks/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from .base_pb import BasePB 4 | from .null_pb import NullPB 5 | from .video_pb import VideoPB 6 | from .frame_pb import FramePB 7 | from .action_pb import ActionPB 8 | 9 | 10 | def create_pb(playback_settings, **kwargs): 11 | playback_type = playback_settings.playback_type 12 | if playback_type == 'none': 13 | return NullPB(None) 14 | elif playback_type == 'by_video': 15 | return VideoPB(playback_settings['by_video'], **kwargs) 16 | elif playback_type == 'by_frame': 17 | return FramePB(playback_settings['by_frame'], **kwargs) 18 | elif playback_type == 'by_action': 19 | return ActionPB(playback_settings['by_action'], **kwargs) 20 | else: 21 | raise NotImplementedError 22 | -------------------------------------------------------------------------------- /gobigger/playbacks/action_pb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | import numpy as np 4 | import logging 5 | import uuid 6 | import copy 7 | import pickle 8 | import lz4.frame 9 | 10 | from .base_pb import BasePB 11 | 12 | 13 | class ActionPB(BasePB): 14 | 15 | def __init__(self, playback_settings, **kwargs): 16 | self.playback_settings = playback_settings 17 | self.save_action = self.playback_settings.save_action 18 | self.save_dir = self.playback_settings.save_dir 19 | self.save_name_prefix = self.playback_settings.save_name_prefix 20 | if self.save_action: 21 | if not os.path.isdir(self.save_dir): 22 | try: 23 | os.makedirs(self.save_dir) 24 | except: 25 | pass 26 | logging.warning('save_dir={} must be an existed directory!'.format(self.save_dir)) 27 | if not self.save_name_prefix: 28 | self.save_name_prefix = str(uuid.uuid1()) 29 | self.playback_data = {} 30 | logging.warning('`by_action` is not available now, please use `by_video` or `by_frame`.') 31 | 32 | def need_save(self, *args, **kwargs): 33 | return self.save_action 34 | 35 | def save_step(self, actions, last_frame_count): 36 | self.playback_data[last_frame_count] = actions 37 | 38 | def save_final(self, cfg, seed): 39 | self.playback_data['cfg'] = cfg 40 | self.playback_data['seed'] = seed 41 | self.playback_path = os.path.join(self.save_dir, self.save_name_prefix + '.ac') 42 | compressed_data = lz4.frame.compress(pickle.dumps(self.playback_data)) 43 | with open(self.playback_path, 'wb') as f: 44 | pickle.dump(compressed_data, f) 45 | logging.info('save ac at {}'.format(self.playback_path)) 46 | -------------------------------------------------------------------------------- /gobigger/playbacks/base_pb.py: -------------------------------------------------------------------------------- 1 | class BasePB: 2 | 3 | def __init__(self, playback_settings): 4 | self.playback_settings = playback_settings 5 | 6 | def need_save(self, last_frame_count, *args, **kwargs): 7 | raise NotImplementedError 8 | 9 | def save_step(self, *args, **kwargs): 10 | raise NotImplementedError 11 | 12 | def save_final(self, *args, **kwargs): 13 | raise NotImplementedError 14 | -------------------------------------------------------------------------------- /gobigger/playbacks/frame_pb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | import numpy as np 4 | import logging 5 | import uuid 6 | import copy 7 | import pickle 8 | import lz4.frame 9 | 10 | from .base_pb import BasePB 11 | 12 | 13 | class FramePB(BasePB): 14 | 15 | def __init__(self, playback_settings, **kwargs): 16 | self.playback_settings = playback_settings 17 | self.save_frame = self.playback_settings.save_frame 18 | self.save_all = self.playback_settings.save_all 19 | self.save_partial = self.playback_settings.save_partial 20 | self.save_dir = self.playback_settings.save_dir 21 | self.save_name_prefix = self.playback_settings.save_name_prefix 22 | if self.save_frame: 23 | if not os.path.isdir(self.save_dir): 24 | try: 25 | os.makedirs(self.save_dir) 26 | except: 27 | pass 28 | logging.warning('save_dir={} must be an existed directory!'.format(self.save_dir)) 29 | if not self.save_name_prefix: 30 | self.save_name_prefix = str(uuid.uuid1()) 31 | self.playback_data = {} 32 | 33 | def need_save(self, *args, **kwargs): 34 | return self.save_frame 35 | 36 | def save_step(self, diff_balls_remove, diff_balls_modify, leaderboard, last_frame_count, *args, **kwargs): 37 | self.playback_data[last_frame_count] = [diff_balls_modify, diff_balls_remove, leaderboard] 38 | 39 | def save_final(self, cfg, *args, **kwargs): 40 | if self.save_frame: 41 | self.playback_data['cfg'] = cfg 42 | self.playback_path = os.path.join(self.save_dir, self.save_name_prefix + '.pb') 43 | compressed_data = lz4.frame.compress(pickle.dumps(self.playback_data)) 44 | with open(self.playback_path, 'wb') as f: 45 | pickle.dump(compressed_data, f) 46 | logging.info('save pb at {}'.format(self.playback_path)) 47 | 48 | -------------------------------------------------------------------------------- /gobigger/playbacks/null_pb.py: -------------------------------------------------------------------------------- 1 | from .base_pb import BasePB 2 | 3 | 4 | class NullPB(BasePB): 5 | 6 | def need_save(self, *args, **kwargs): 7 | return False 8 | 9 | def save_step(self, *args, **kwargs): 10 | return 11 | 12 | def save_final(self, *args, **kwargs): 13 | return 14 | -------------------------------------------------------------------------------- /gobigger/playbacks/tests/test_playback.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import pytest 4 | import uuid 5 | from pygame.math import Vector2 6 | import pygame 7 | import random 8 | import time 9 | from easydict import EasyDict 10 | 11 | from gobigger.envs import create_env 12 | from gobigger.agents import BotAgent 13 | 14 | logging.basicConfig(level=logging.DEBUG) 15 | 16 | 17 | @pytest.mark.unittest 18 | class TestPlayback: 19 | 20 | def test_none_pb(self): 21 | env = create_env('st_t2p2', dict( 22 | frame_limit=100, 23 | playback_settings=dict( 24 | playback_type='none', 25 | ), 26 | )) 27 | obs = env.reset() 28 | bot_agents = [] 29 | team_infos = env.get_team_infos() 30 | print(team_infos) 31 | for team_id, player_ids in team_infos: 32 | for player_id in player_ids: 33 | bot_agents.append(BotAgent(player_id, level=2)) 34 | time_step_all = 0 35 | for i in range(100000): 36 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 37 | t1 = time.time() 38 | obs, reward, done, info = env.step(actions=actions) 39 | t2 = time.time() 40 | time_step_all += t2-t1 41 | logging.debug('{} {:.4f} envstep {:.3f} / {:.3f}, leaderboard={}'\ 42 | .format(i, obs[0]['last_frame_count'], t2-t1, time_step_all/(i+1), obs[0]['leaderboard'])) 43 | if done: 44 | logging.debug('Game Over') 45 | break 46 | env.close() 47 | 48 | def test_video_pb(self): 49 | env = create_env('st_t2p2', dict( 50 | frame_limit=100, 51 | playback_settings=dict( 52 | playback_type='by_video', 53 | by_video=dict( 54 | save_video=True, 55 | ), 56 | ), 57 | )) 58 | obs = env.reset() 59 | bot_agents = [] 60 | team_infos = env.get_team_infos() 61 | print(team_infos) 62 | for team_id, player_ids in team_infos: 63 | for player_id in player_ids: 64 | bot_agents.append(BotAgent(player_id, level=2)) 65 | time_step_all = 0 66 | for i in range(100000): 67 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 68 | t1 = time.time() 69 | obs, reward, done, info = env.step(actions=actions) 70 | t2 = time.time() 71 | time_step_all += t2-t1 72 | logging.debug('{} {:.4f} envstep {:.3f} / {:.3f}, leaderboard={}'\ 73 | .format(i, obs[0]['last_frame_count'], t2-t1, time_step_all/(i+1), obs[0]['leaderboard'])) 74 | if done: 75 | logging.debug('Game Over') 76 | break 77 | env.close() 78 | assert os.path.isfile('test-all.mp4') 79 | os.remove('test-all.mp4') 80 | 81 | def test_frame_pb(self): 82 | env = create_env('st_t2p2', dict( 83 | frame_limit=100, 84 | playback_settings=dict( 85 | playback_type='by_frame', 86 | by_frame=dict( 87 | save_frame=True, 88 | ) 89 | ), 90 | )) 91 | obs = env.reset() 92 | bot_agents = [] 93 | team_infos = env.get_team_infos() 94 | print(team_infos) 95 | for team_id, player_ids in team_infos: 96 | for player_id in player_ids: 97 | bot_agents.append(BotAgent(player_id, level=2)) 98 | time_step_all = 0 99 | for i in range(100000): 100 | actions = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} 101 | t1 = time.time() 102 | obs, reward, done, info = env.step(actions=actions) 103 | t2 = time.time() 104 | time_step_all += t2-t1 105 | logging.debug('{} {:.4f} envstep {:.3f} / {:.3f}, leaderboard={}'\ 106 | .format(i, obs[0]['last_frame_count'], t2-t1, time_step_all/(i+1), obs[0]['leaderboard'])) 107 | if done: 108 | logging.debug('Game Over') 109 | break 110 | env.close() 111 | assert os.path.isfile('test.pb') 112 | os.remove('test.pb') 113 | -------------------------------------------------------------------------------- /gobigger/playbacks/video_pb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | import numpy as np 4 | import logging 5 | import uuid 6 | import copy 7 | import pickle 8 | 9 | from gobigger.render import EnvRender 10 | from .base_pb import BasePB 11 | 12 | 13 | class VideoPB(BasePB): 14 | 15 | def __init__(self, playback_settings, **kwargs): 16 | self.playback_settings = playback_settings 17 | self.fps = kwargs['fps'] 18 | self.map_width = kwargs['map_width'] 19 | self.map_height = kwargs['map_height'] 20 | self.save_video = self.playback_settings.save_video 21 | self.save_fps = self.playback_settings.save_fps 22 | self.save_resolution = self.playback_settings.save_resolution 23 | self.save_all = self.playback_settings.save_all 24 | self.save_partial = self.playback_settings.save_partial 25 | self.save_dir = self.playback_settings.save_dir 26 | self.save_name_prefix = self.playback_settings.save_name_prefix 27 | if self.save_video: 28 | if not os.path.isdir(self.save_dir): 29 | try: 30 | os.makedirs(self.save_dir) 31 | except: 32 | pass 33 | logging.warning('save_dir={} must be an existed directory!'.format(self.save_dir)) 34 | if not self.save_name_prefix: 35 | self.save_name_prefix = str(uuid.uuid1()) 36 | self.save_fps = int(self.save_fps) 37 | self.save_resolution = int(self.save_resolution) 38 | self.save_freq = self.fps // self.save_fps 39 | self.render = EnvRender(game_screen_width=self.save_resolution, game_screen_height=self.save_resolution, 40 | map_width=self.map_width, map_height=self.map_width) 41 | self.screens_all = [] 42 | self.screens_partial = [] 43 | 44 | def get_clip_screen(self, screen_data, rectangle): 45 | rectangle_tmp = copy.deepcopy(rectangle) 46 | left_top_x, left_top_y, right_bottom_x, right_bottom_y = rectangle_tmp 47 | left_top_x_fix = max(left_top_x, 0) 48 | left_top_y_fix = max(left_top_y, 0) 49 | right_bottom_x_fix = min(right_bottom_x, self.width) 50 | right_bottom_y_fix = min(right_bottom_y, self.height) 51 | 52 | if len(screen_data.shape) == 3: 53 | screen_data_clip = screen_data[left_top_x_fix:right_bottom_x_fix, 54 | left_top_y_fix:right_bottom_y_fix, :] 55 | screen_data_clip = np.pad(screen_data_clip, 56 | ((left_top_x_fix-left_top_x,right_bottom_x-right_bottom_x_fix), 57 | (left_top_y_fix-left_top_y,right_bottom_y-right_bottom_y_fix), 58 | (0,0)), 59 | mode='constant') 60 | elif len(screen_data.shape) == 2: 61 | screen_data_clip = screen_data[left_top_x_fix:right_bottom_x_fix, 62 | left_top_y_fix:right_bottom_y_fix] 63 | screen_data_clip = np.pad(screen_data_clip, 64 | ((left_top_x_fix-left_top_x,right_bottom_x-right_bottom_x_fix), 65 | (left_top_y_fix-left_top_y,right_bottom_y-right_bottom_y_fix)), 66 | mode='constant') 67 | else: 68 | raise NotImplementedError 69 | return screen_data_clip 70 | 71 | def need_save(self, last_frame_count, *args, **kwargs): 72 | return self.save_video and last_frame_count % self.save_freq == 0 73 | 74 | def save_step(self, food_balls, thorns_balls, spore_balls, players, player_num_per_team, *args, **kwargs): 75 | self.screens_all.append(self.render.get_screen(food_balls, thorns_balls, spore_balls, players, player_num_per_team)) 76 | 77 | def save_final(self, *args, **kwargs): 78 | if self.save_video: 79 | if self.save_all: 80 | video_file_all = os.path.join(self.save_dir, '{}-all.mp4'.format(self.save_name_prefix)) 81 | out = cv2.VideoWriter(video_file_all, cv2.VideoWriter_fourcc(*'mp4v'), self.save_fps, 82 | (self.screens_all[0].shape[1], self.screens_all[0].shape[0])) 83 | for index, screen in enumerate(self.screens_all): 84 | out.write(screen) 85 | out.release() 86 | cv2.destroyAllWindows() 87 | if self.save_partial: 88 | for player_id, screens in self.screens_partial.items(): 89 | video_file_partial = os.path.join(self.save_dir, '{}-{:02d}.mp4'.format(self.save_name_prefix, player_id)) 90 | out = cv2.VideoWriter(video_file_partial, cv2.VideoWriter_fourcc(*'mp4v'), self.save_fps, 91 | (screens[0].shape[1], screens[0].shape[0])) 92 | for index, screen in enumerate(self.screens): 93 | if index % self.save_freq == 0: 94 | out.write(screen) 95 | out.release() 96 | cv2.destroyAllWindows() 97 | -------------------------------------------------------------------------------- /gobigger/players/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_player import BasePlayer 2 | from .human_player import HumanPlayer 3 | from .human_sp_player import HumanSPPlayer -------------------------------------------------------------------------------- /gobigger/players/base_player.py: -------------------------------------------------------------------------------- 1 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 2 | 3 | 4 | class BasePlayer: 5 | ''' 6 | Player's abstract class 7 | ''' 8 | def __init__(self, name=None): 9 | self.name = name 10 | 11 | def move(self, direction): 12 | ''' 13 | Parameters: 14 | direction : Given any point in a unit circle, the angle represents the direction, and the magnitude represents the acceleration 15 | ''' 16 | raise NotImplementedError 17 | 18 | def eject(self): 19 | ''' 20 | Do sporulation 21 | ''' 22 | raise NotImplementedError 23 | 24 | def eat(self, ball): 25 | ''' 26 | Eat another ball 27 | ''' 28 | raise NotImplementedError 29 | 30 | def stop(self): 31 | ''' 32 | stop moving 33 | ''' 34 | raise NotImplementedError 35 | 36 | def respawn(self): 37 | raise NotImplementedError -------------------------------------------------------------------------------- /gobigger/players/human_sp_player.py: -------------------------------------------------------------------------------- 1 | from gobigger.balls import FoodBall, ThornsBall, CloneBall, SporeBall 2 | from .human_player import HumanPlayer 3 | 4 | 5 | class HumanSPPlayer(HumanPlayer): 6 | 7 | def __init__(self, cfg, team_id, player_id, border, spore_settings, sequence_generator=None): 8 | super(HumanSPPlayer, self).__init__(cfg, team_id, player_id, border, spore_settings) 9 | assert sequence_generator is not None 10 | self.sequence_generator = sequence_generator 11 | 12 | def move(self, ball_id=None, direction=None, duration=0.05): 13 | if ball_id is None: 14 | for ball_id, ball in self.balls.items(): 15 | ball.move(given_acc=direction, duration=duration) 16 | ball.score_decay() 17 | else: 18 | if ball_id in self.balls: 19 | self.balls[ball_id].move(given_acc=direction, duration=duration) 20 | self.balls[ball_id].score_decay() 21 | 22 | def eject(self, ball_id=None, direction=None): 23 | ret = [] 24 | if ball_id and ball_id in self.balls: 25 | ret.append(self.balls[ball_id].eject(direction=direction)) 26 | return ret 27 | 28 | def split(self, ball_id=None, direction=None): 29 | if ball_id and ball_id in self.balls: 30 | ret = self.balls[ball_id].split(self.get_clone_num(), direction=direction) 31 | if ret and isinstance(ret, CloneBall): 32 | self.add_balls(ret) 33 | return True 34 | 35 | def respawn(self, position): 36 | ball = CloneBall(ball_id=self.sequence_generator.get(), position=position, border=self.border, 37 | score=self.ball_settings.score_respawn, team_id=self.team_id, 38 | player_id=self.player_id, spore_settings=self.spore_settings, 39 | sequence_generator=self.sequence_generator, **self.ball_settings) 40 | self.balls = {} 41 | self.balls[ball.ball_id] = ball 42 | return True 43 | -------------------------------------------------------------------------------- /gobigger/players/tests/test_base_player.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | 6 | from gobigger.players import BasePlayer 7 | from gobigger.balls import ThornsBall, SporeBall, CloneBall, FoodBall 8 | from gobigger.utils import Border 9 | from gobigger.server import Server 10 | 11 | logging.basicConfig(level=logging.DEBUG) 12 | 13 | @pytest.mark.unittest 14 | class TestBasePlayer: 15 | 16 | def test_all(self): 17 | base_player = BasePlayer(name='test') 18 | with pytest.raises(Exception) as e: 19 | base_player.move(direction=None) 20 | with pytest.raises(Exception) as e: 21 | base_player.eject() 22 | with pytest.raises(Exception) as e: 23 | base_player.eat(ball=None) 24 | with pytest.raises(Exception) as e: 25 | base_player.stop() 26 | with pytest.raises(Exception) as e: 27 | base_player.respawn() 28 | -------------------------------------------------------------------------------- /gobigger/render/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_render import BaseRender 2 | from .env_render import EnvRender 3 | from .realtime_render import RealtimeRender, RealtimePartialRender 4 | from .pb_render import PBRender, TkSelect 5 | -------------------------------------------------------------------------------- /gobigger/render/base_render.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pygame 3 | import platform 4 | 5 | 6 | class BaseRender: 7 | 8 | def __init__(self, game_screen_width, game_screen_height, info_width=0, info_height=0, with_show=False): 9 | pygame.init() 10 | if platform.system() == 'Linux': # If the current system is linux, window is not used 11 | os.environ["SDL_VIDEODRIVER"] = "dummy" 12 | self.game_screen_width = game_screen_width 13 | self.game_screen_height = game_screen_height 14 | self.total_screen_width = game_screen_width + info_width 15 | self.total_screen_height = game_screen_height + info_height 16 | # self.FPS = 60 # Set the frame rate (the number of screen refreshes per second) 17 | # self.fpsClock = pygame.time.Clock() 18 | if with_show: 19 | self.screen = pygame.display.set_mode((self.total_screen_width, self.total_screen_height), 0, 32) 20 | pygame.display.set_caption("GoBigger - OpenDILab Environment") 21 | 22 | def fill(self, server): 23 | raise NotImplementedError 24 | 25 | def show(self): 26 | raise NotImplementedError 27 | 28 | def close(self): 29 | raise NotImplementedError 30 | -------------------------------------------------------------------------------- /gobigger/render/tests/test_env_render.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import pygame 6 | import random 7 | from easydict import EasyDict 8 | 9 | from gobigger.balls import BaseBall, SporeBall 10 | from gobigger.players import HumanPlayer 11 | from gobigger.utils import Border 12 | from gobigger.server import Server 13 | from gobigger.render import EnvRender 14 | 15 | logging.basicConfig(level=logging.DEBUG) 16 | 17 | 18 | @pytest.mark.unittest 19 | class TestEnvRender: 20 | 21 | def test_init(self): 22 | render = EnvRender() 23 | assert True 24 | 25 | def test_fill_all(self): 26 | border = Border(0, 0, 1000, 1000) 27 | render = EnvRender() 28 | food_balls = [BaseBall('0', border.sample(), border=border, score=100)] 29 | thorns_balls = [BaseBall('0', border.sample(), border=border, score=10000)] 30 | spore_balls = [BaseBall('0', border.sample(), border=border, score=1400)] 31 | players = [HumanPlayer(cfg=Server.default_config().manager_settings.player_manager.ball_settings, 32 | team_id=0, player_id=0, border=border, 33 | spore_settings=Server.default_config().manager_settings.spore_manager.ball_settings)] 34 | screen_data_all = render.get_screen(food_balls, thorns_balls, spore_balls, players, 1) 35 | assert len(screen_data_all.shape) == 3 36 | -------------------------------------------------------------------------------- /gobigger/render/tests/test_realtime_render.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import pygame 6 | import random 7 | 8 | from gobigger.balls import BaseBall 9 | from gobigger.players import HumanPlayer 10 | from gobigger.utils import Border 11 | from gobigger.server import Server 12 | from gobigger.render import RealtimeRender, RealtimePartialRender 13 | 14 | logging.basicConfig(level=logging.DEBUG) 15 | 16 | 17 | @pytest.mark.unittest 18 | class TestRealtimePartialRender: 19 | 20 | def test_init(self): 21 | render = RealtimeRender() 22 | render = RealtimePartialRender() 23 | assert True 24 | -------------------------------------------------------------------------------- /gobigger/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .server import Server 2 | from .server_sp import ServerSP 3 | -------------------------------------------------------------------------------- /gobigger/server/tests/test_server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import pygame 6 | import random 7 | import numpy as np 8 | import cv2 9 | import multiprocessing as mp 10 | 11 | from gobigger.utils import Border 12 | from gobigger.server import Server 13 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 14 | 15 | logging.basicConfig(level=logging.DEBUG) 16 | 17 | 18 | @pytest.mark.unittest 19 | class TestServer: 20 | 21 | def test_init(self): 22 | server = Server() 23 | assert True 24 | 25 | def test_spawn_balls(self): 26 | server = Server() 27 | server.reset() 28 | 29 | def test_step_control_random(self): 30 | server = Server() 31 | server.reset() 32 | fps_set = 20 33 | clock = pygame.time.Clock() 34 | render = RealtimePartialRender() 35 | for i in range(10): 36 | actions = {player_name: [random.uniform(-1, 1), random.uniform(-1, 1), -1] \ 37 | for player_name in server.get_player_names()} 38 | done = server.step(actions=actions) 39 | obs = server.obs() 40 | render.fill(obs[0], obs[1][0], player_num_per_team=1, fps=10) 41 | render.show() 42 | clock.tick(fps_set) 43 | server.close() 44 | 45 | def test_obs(self): 46 | server = Server() 47 | server.reset() 48 | for i in range(10): 49 | actions = {player_name: [random.uniform(-1, 1), random.uniform(-1, 1), -1] \ 50 | for player_name in server.get_player_names()} 51 | done = server.step(actions=actions) 52 | obs = server.obs() 53 | logging.debug(obs[0]) 54 | 55 | def test_obs_multi_player(self): 56 | server = Server(dict( 57 | team_num=1, 58 | player_num_per_team=2, 59 | )) 60 | server.reset() 61 | for i in range(10): 62 | actions = {player_name: [random.uniform(-1, 1), random.uniform(-1, 1), -1] \ 63 | for player_name in server.get_player_names()} 64 | done = server.step(actions=actions) 65 | obs = server.obs() 66 | logging.debug(obs[0]) 67 | 68 | def test_multiprocessing(self): 69 | ''' 70 | Overview: 71 | Test the server in a multi-process environment 72 | ''' 73 | server_num = 2 74 | servers = [] 75 | for i in range(server_num): 76 | server = Server(dict( 77 | team_num=1, 78 | player_num_per_team=1, 79 | match_time=60*1, 80 | )) 81 | server.reset() 82 | servers.append(server) 83 | 84 | def run(server_index): 85 | for i in range(server_num): 86 | actions = {player_name: [random.uniform(-1, 1), random.uniform(-1, 1), -1] \ 87 | for player_name in servers[server_index].get_player_names()} 88 | done = servers[server_index].step(actions=actions) 89 | global_state, players_obs, info = servers[server_index].obs() 90 | logging.debug('{} {} {}'.format(server_index, i, global_state)) 91 | logging.debug('{} start close'.format(server_index)) 92 | logging.debug('{} finish'.format(server_index)) 93 | 94 | ps = [] 95 | for i in range(server_num): 96 | p = mp.Process(target=run, args=(i,), daemon=True) 97 | ps.append(p) 98 | 99 | for p in ps: 100 | p.start() 101 | 102 | for p in ps: 103 | p.join() 104 | 105 | -------------------------------------------------------------------------------- /gobigger/server/tests/test_server_sp.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import uuid 4 | from pygame.math import Vector2 5 | import pygame 6 | import random 7 | import numpy as np 8 | import cv2 9 | import multiprocessing as mp 10 | 11 | from gobigger.utils import Border 12 | from gobigger.server import ServerSP 13 | from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender 14 | 15 | logging.basicConfig(level=logging.DEBUG) 16 | 17 | 18 | @pytest.mark.unittest 19 | class TestServerSP: 20 | 21 | def test_init(self): 22 | server = ServerSP() 23 | assert True 24 | 25 | def test_spawn_balls(self): 26 | server = ServerSP() 27 | server.reset() 28 | 29 | def test_step_control_random(self): 30 | server = ServerSP() 31 | server.reset() 32 | obs = server.obs() 33 | fps_set = 20 34 | clock = pygame.time.Clock() 35 | render = RealtimePartialRender() 36 | for i in range(10): 37 | actions = {player_name: {ball[-1]: [random.uniform(-1, 1), random.uniform(-1, 1), -1]\ 38 | for ball in obs[1][0]['overlap']['clone']} \ 39 | for player_name in server.get_player_names()} 40 | done = server.step(actions=actions) 41 | obs = server.obs() 42 | render.fill(obs[0], obs[1][0], player_num_per_team=1, fps=10) 43 | render.show() 44 | clock.tick(fps_set) 45 | server.close() 46 | 47 | def test_obs(self): 48 | server = ServerSP() 49 | server.reset() 50 | obs = server.obs() 51 | for i in range(10): 52 | actions = {player_name: {ball[-1]: [random.uniform(-1, 1), random.uniform(-1, 1), -1]\ 53 | for ball in obs[1][0]['overlap']['clone']} \ 54 | for player_name in server.get_player_names()} 55 | done = server.step(actions=actions) 56 | obs = server.obs() 57 | logging.debug(obs[0]) 58 | 59 | def test_obs_multi_player(self): 60 | server = ServerSP(dict( 61 | team_num=1, 62 | player_num_per_team=2, 63 | )) 64 | server.reset() 65 | obs = server.obs() 66 | for i in range(10): 67 | actions = {player_name: {ball[-1]: [random.uniform(-1, 1), random.uniform(-1, 1), -1]\ 68 | for ball in obs[1][0]['overlap']['clone']} \ 69 | for player_name in server.get_player_names()} 70 | done = server.step(actions=actions) 71 | obs = server.obs() 72 | logging.debug(obs[0]) 73 | -------------------------------------------------------------------------------- /gobigger/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .structures import format_vector, add_score, save_screen_data_to_img, Border, QuadNode 2 | from .collision_detection import create_collision_detection 3 | from .config_utils import deep_merge_dicts 4 | from .tool import * 5 | from .colors import * 6 | from .obs_utils import PlayerStatesUtil, PlayerStatesSPUtil 7 | # from .playback_utils import PlaybackUtil 8 | from .generator_utils import SequenceGenerator 9 | -------------------------------------------------------------------------------- /gobigger/utils/colors.py: -------------------------------------------------------------------------------- 1 | FOOD_COLOR_GRAYSCALE = (1) 2 | THORNS_COLOR_GRAYSCALE = (2) 3 | SPORE_COLOR_GRAYSCALE = (3) 4 | PLAYER_COLORS_GRAYSCALE = [(i) for i in range(4, 100)] 5 | BACKGROUND_GRAYSCALE = (255) 6 | 7 | FOOD_COLOR = (253, 246, 227) 8 | THORNS_COLOR = (107, 194, 12) 9 | SPORE_COLOR = (255, 153, 18) 10 | PLAYER_COLORS = [ 11 | [ 12 | (38,139,210), # blue 13 | ], 14 | [ 15 | (254,223,2), # yellow 16 | ], 17 | [ 18 | (177,109,202), # purple 19 | ], 20 | [ 21 | (42,161,152), # green 22 | ], 23 | [ 24 | (203,75,22), # orange 25 | ], 26 | [ 27 | (195,206,231), # gray 28 | ], 29 | [ 30 | (128,128,128), # gray2 31 | ], 32 | [ 33 | (128,0,0), # maroon 34 | ], 35 | [ 36 | (128,128,0), # olive 37 | ], 38 | [ 39 | (0,128,0), # green2 40 | ], 41 | [ 42 | (128,0,128), # purple2 43 | ], 44 | [ 45 | (0,128,128), # teal 46 | ], 47 | [ 48 | (0,0,128), # navy 49 | ], 50 | [ 51 | (255,127,80), # coral 52 | ], 53 | [ 54 | (255,215,0), # goal 55 | ], 56 | [ 57 | (154,205,50), # yellow green 58 | ], 59 | ] 60 | BACKGROUND = (0, 43, 54) 61 | 62 | GRAY = (220, 220, 220) 63 | BLACK = (0, 0, 0) 64 | RED = (255, 0, 0) 65 | YELLOW = (255, 153, 18) 66 | GREEN = (0, 255, 0) 67 | PURPLE = (160, 32, 240) 68 | WHITE = (255,255,255) 69 | -------------------------------------------------------------------------------- /gobigger/utils/config_utils.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from typing import List, Optional 3 | 4 | 5 | def deep_merge_dicts(original: dict, new_dict: dict) -> dict: 6 | """ 7 | Overview: 8 | Merge two dicts by calling ``deep_update`` 9 | Arguments: 10 | - original (:obj:`dict`): Dict 1. 11 | - new_dict (:obj:`dict`): Dict 2. 12 | Returns: 13 | - merged_dict (:obj:`dict`): A new dict that is d1 and d2 deeply merged. 14 | """ 15 | original = original or {} 16 | new_dict = new_dict or {} 17 | merged = copy.deepcopy(original) 18 | if new_dict: # if new_dict is neither empty dict nor None 19 | deep_update(merged, new_dict, True, []) 20 | return merged 21 | 22 | 23 | def deep_update( 24 | original: dict, 25 | new_dict: dict, 26 | new_keys_allowed: bool = False, 27 | whitelist: Optional[List[str]] = None, 28 | override_all_if_type_changes: Optional[List[str]] = None 29 | ): 30 | """ 31 | Overview: 32 | Update original dict with values from new_dict recursively. 33 | Arguments: 34 | - original (:obj:`dict`): Dictionary with default values. 35 | - new_dict (:obj:`dict`): Dictionary with values to be updated 36 | - new_keys_allowed (:obj:`bool`): Whether new keys are allowed. 37 | - whitelist (:obj:`Optional[List[str]]`): 38 | List of keys that correspond to dict 39 | values where new subkeys can be introduced. This is only at the top 40 | level. 41 | - override_all_if_type_changes(:obj:`Optional[List[str]]`): 42 | List of top level 43 | keys with value=dict, for which we always simply override the 44 | entire value (:obj:`dict`), if the "type" key in that value dict changes. 45 | 46 | .. note:: 47 | 48 | If new key is introduced in new_dict, then if new_keys_allowed is not 49 | True, an error will be thrown. Further, for sub-dicts, if the key is 50 | in the whitelist, then new subkeys can be introduced. 51 | """ 52 | whitelist = whitelist or [] 53 | override_all_if_type_changes = override_all_if_type_changes or [] 54 | 55 | for k, value in new_dict.items(): 56 | if k not in original and not new_keys_allowed: 57 | raise RuntimeError("Unknown config parameter `{}`. Base config have: {}.".format(k, original.keys())) 58 | 59 | # Both original value and new one are dicts. 60 | if isinstance(original.get(k), dict) and isinstance(value, dict): 61 | # Check old type vs old one. If different, override entire value. 62 | if k in override_all_if_type_changes and \ 63 | "type" in value and "type" in original[k] and \ 64 | value["type"] != original[k]["type"]: 65 | original[k] = value 66 | # Whitelisted key -> ok to add new subkeys. 67 | elif k in whitelist: 68 | deep_update(original[k], value, True) 69 | # Non-whitelisted key. 70 | else: 71 | deep_update(original[k], value, new_keys_allowed) 72 | # Original value not a dict OR new value not a dict: 73 | # Override entire value. 74 | else: 75 | original[k] = value 76 | return original 77 | -------------------------------------------------------------------------------- /gobigger/utils/frame_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pickle 4 | 5 | 6 | def save_frame_info(self, save_frame_full_path, food_balls, thorns_balls, spore_balls, clone_balls): 7 | if save_frame_full_path != '': 8 | frame_info = {'food': [], 'thorns': [], 'spore': [], 'clone': []} 9 | # food 10 | for ball in food_balls: 11 | frame_info['food'].append([ball.position.x, ball.position.y, ball.radius]) 12 | # thorns 13 | for ball in thorns_balls: 14 | frame_info['thorns'].append([ball.position.x, ball.position.y, ball.radius, ball.vel.x, ball.vel.y, 15 | ball.acc.x, ball.acc.y, ball.move_time, ball.moving]) 16 | # spore 17 | for ball in spore_balls: 18 | frame_info['spore'].append([ball.position.x, ball.position.y, ball.radius, ball.direction.x, 19 | ball.direction.y, ball.vel.x, ball.vel.y, 20 | ball.acc.x, ball.acc.y, ball.move_time, ball.moving]) 21 | # clone 22 | for ball in clone_balls: 23 | frame_info['clone'].append([ball.position.x, ball.position.y, ball.radius, ball.owner, 24 | ball.team_name, ball.vel.x, ball.vel.y, ball.acc.x, ball.acc.y, 25 | ball.vel_last.x, ball.vel_last.y, ball.acc_last.x, ball.acc_last.y, 26 | ball.direction.x, ball.direction.y, ball.last_given_acc.x, 27 | ball.last_given_acc.y, ball.age, ball.cooling_last, ball.stop_flag, 28 | ball.stop_time, ball.acc_stop.x, ball.acc_stop.y]) 29 | with open(save_frame_full_path, 'wb') as f: 30 | pickle.dump(frame_info, f) 31 | 32 | def load_frame_info(): 33 | custom_init_food = [] 34 | custom_init_thorns = [] 35 | custom_init_spore = [] 36 | custom_init_clone = [] 37 | if frame_path: 38 | with open(frame_path, 'rb') as f: 39 | data = pickle.load(f) 40 | custom_init_food = data['food'] 41 | custom_init_thorns = data['thorns'] 42 | custom_init_spore = data['spore'] 43 | custom_init_clone = data['clone'] 44 | return custom_init_food, custom_init_thorns, custom_init_spore, custom_init_clone -------------------------------------------------------------------------------- /gobigger/utils/generator_utils.py: -------------------------------------------------------------------------------- 1 | class SequenceGenerator: 2 | 3 | def __init__(self, start=0): 4 | self.start = 0 5 | 6 | def get(self): 7 | ret = self.start 8 | self.start += 1 9 | return ret 10 | 11 | -------------------------------------------------------------------------------- /gobigger/utils/test/test_config_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import pytest 4 | from easydict import EasyDict 5 | 6 | from gobigger.utils import deep_merge_dicts 7 | 8 | logging.basicConfig(level=logging.DEBUG) 9 | 10 | 11 | def test_deep_merge_dicts(): 12 | a = EasyDict(dict( 13 | name='aaa', 14 | content=dict( 15 | team_num=4, 16 | map_width=1000 17 | ) 18 | )) 19 | b = EasyDict(dict( 20 | name='bbb', 21 | content=dict( 22 | map_width=2000 23 | ) 24 | )) 25 | c = deep_merge_dicts(a, b) 26 | assert c.name == 'bbb' 27 | assert c.content.map_width == 2000 28 | assert c.content.team_num == 4 29 | -------------------------------------------------------------------------------- /gobigger/utils/test/test_sequence_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import pytest 4 | import uuid 5 | from pygame.math import Vector2 6 | import pygame 7 | import random 8 | import numpy as np 9 | import cv2 10 | 11 | from gobigger.utils import SequenceGenerator 12 | 13 | logging.basicConfig(level=logging.DEBUG) 14 | 15 | 16 | def test_sequence_generator(): 17 | class Temp: 18 | def __init__(self, sequence_generator=None): 19 | self.sequence_generator = sequence_generator 20 | def generate(self): 21 | return self.sequence_generator.get() 22 | sequence_generator = SequenceGenerator(0) 23 | ts = [Temp(sequence_generator) for i in range(5)] 24 | for index, t in enumerate(ts): 25 | assert t.generate() == index 26 | -------------------------------------------------------------------------------- /gobigger/utils/test/test_structures.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import pytest 4 | import uuid 5 | from pygame.math import Vector2 6 | import pygame 7 | import random 8 | import numpy as np 9 | import cv2 10 | 11 | from gobigger.balls import BaseBall 12 | from gobigger.utils import format_vector, add_score, save_screen_data_to_img, Border, QuadNode 13 | 14 | logging.basicConfig(level=logging.DEBUG) 15 | 16 | 17 | def test_format_vector(): 18 | v = Vector2(6, 8) 19 | norm_max = 5 20 | v_format = format_vector(v, norm_max=norm_max) 21 | assert v_format.x == 3 22 | assert v_format.y == 4 23 | 24 | 25 | def test_add_score(): 26 | score_old = 10 27 | score_add = 20 28 | score_new = add_score(score_old, score_add) 29 | assert score_new == 30 30 | 31 | 32 | def test_save_screen_data_to_img(): 33 | screen_data = (np.random.rand(100, 100, 3) * 255).astype(np.uint8) 34 | img_path = './temp.jpg' 35 | save_screen_data_to_img(screen_data, img_path=None) 36 | assert True 37 | 38 | 39 | @pytest.mark.unittest 40 | class TestBorder: 41 | 42 | def test_init(self): 43 | border = Border(0, 0, 1000, 1000) 44 | assert border.minx == 0 45 | assert border.miny == 0 46 | assert border.maxx == 1000 47 | assert border.maxy == 1000 48 | assert border.width == 1000 49 | assert border.height == 1000 50 | 51 | def test_contains(self): 52 | border = Border(0, 0, 1000, 1000) 53 | assert border.contains(position=Vector2(300, 300)) 54 | assert not border.contains(position=Vector2(1300, 300)) 55 | 56 | def test_sample(self): 57 | border = Border(0, 0, 1000, 1000) 58 | s = border.sample() 59 | assert border.contains(s) 60 | 61 | def test_get_joint(self): 62 | border = Border(0, 0, 1000, 1000) 63 | border_new = border.get_joint(border=Border(300, 300, 600, 600)) 64 | assert border_new.minx == 300 65 | assert border_new.maxx == 300 66 | assert border_new.miny == 600 67 | assert border_new.maxy == 600 68 | 69 | 70 | @pytest.mark.unittest 71 | class TestQuadNode: 72 | 73 | def test_init(self): 74 | border = Border(0, 0, 1000, 1000) 75 | quad_node = QuadNode(border) 76 | assert quad_node.max_depth == 32 77 | 78 | def test_get_quad(self): 79 | border = Border(0, 0, 1000, 1000) 80 | quad_node = QuadNode(border) 81 | node = BaseBall('0', position=border.sample(), border=border, score=1) 82 | assert isinstance(quad_node.get_quad(node=node), int) 83 | 84 | def test_insert(self): 85 | border = Border(0, 0, 1000, 1000) 86 | quad_node = QuadNode(border) 87 | node = BaseBall('0', position=border.sample(), border=border, score=1) 88 | quad_node.insert(node=node) 89 | 90 | def test_find(self): 91 | border = Border(0, 0, 1000, 1000) 92 | quad_node = QuadNode(border) 93 | node = BaseBall('0', position=border.sample(), border=border, score=1) 94 | quad_node.find(border) 95 | 96 | def test_clear(self): 97 | border = Border(0, 0, 1000, 1000) 98 | quad_node = QuadNode(border) 99 | node = BaseBall('0', position=border.sample(), border=border, score=1) 100 | quad_node.clear() 101 | 102 | def test_remove(self): 103 | border = Border(0, 0, 1000, 1000) 104 | quad_node = QuadNode(border) 105 | node = BaseBall('0', position=border.sample(), border=border, score=1) 106 | quad_node.remove(node=node) 107 | -------------------------------------------------------------------------------- /gobigger/utils/tool.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | import numpy as np 4 | 5 | 6 | def chunks(arr, m): 7 | n = int(math.ceil(len(arr) / float(m))) 8 | return [arr[i:i + n] for i in range(0, len(arr), n)] 9 | 10 | def get_probability(src,arr): 11 | diff = [abs(i-src)+0.001 for i in arr] 12 | return [1/i if 1/i<1 else 1 for i in diff] 13 | 14 | def norm(arr): 15 | return [i/sum(arr) for i in arr] 16 | 17 | def to_aliased_circle(position, radius, cut_num=8, decrease=1): 18 | point_list = [] 19 | radius_decrease = radius - decrease 20 | assert radius_decrease > 0 21 | piece_angle = math.pi / (cut_num) 22 | for i in range(cut_num*2): 23 | angle = piece_angle * i 24 | if i % 2 == 0: 25 | point_list.append([position.x + radius * math.cos(angle), position.y + radius * math.sin(angle)]) 26 | else: 27 | point_list.append([position.x + radius_decrease * math.cos(angle), position.y + radius_decrease * math.sin(angle)]) 28 | return point_list 29 | 30 | def to_arrow(position, radius, direction, out=1.2): 31 | x0, y0 = position.x, position.y 32 | x, y = direction.x, direction.y 33 | point_list = [ 34 | [x0 + out * radius * x, y0 + out * radius * y], 35 | [x0 - math.sqrt(2)/2 * radius * (y - x), y0 + math.sqrt(2)/2 * radius * (x + y)], 36 | [x0 + math.sqrt(2)/2 * radius * (x + y), y0 + math.sqrt(2)/2 * radius * (y - x)], 37 | ] 38 | return point_list 39 | -------------------------------------------------------------------------------- /practice/README.md: -------------------------------------------------------------------------------- 1 | ## Practice 2 | We offer multiple gameplay modes for players to enjoy, including development, battle (Bot and AI) and spectator mode. Our platform supports both single-player and two-player matches. Welcome to explore and enjoy the experience. 3 | 4 | ### Download Weight 5 | ```bash 6 | wget https://opendilab.net/download/GoBigger/solo_agent.pth.tar 7 | wget https://opendilab.net/download/GoBigger/cooperative_agent.pth.tar 8 | ``` 9 | ### Quick Start 10 | 11 | #### Installation 12 | ```bash 13 | git clone https://github.com/opendilab/GoBigger.git 14 | pip install -e . 15 | ``` 16 | 17 | #### Usage 18 | ```bash 19 | python battle.py --mode single --map farm # Single-player development 20 | python battle.py --mode single --map vsbot # Single-player vs. Bot 21 | python battle.py --mode single --map vsai # Single-player vs. Single-AI 22 | python battle.py --mode team --map farm # Two-player development 23 | python battle.py --mode team --map vsbot # Two-player vs. Bot 24 | python battle.py --mode team --map vsai # Two-player vs. Team-AI 25 | python battle.py --mode watch # Spectator mode: Team-AI vs. Team-AI 26 | ``` 27 | -------------------------------------------------------------------------------- /practice/cooperative_agent/agent.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from practice.tools.util import default_collate_with_dim 3 | from practice.tools.features import Features 4 | from practice.cooperative_agent.model import Model 5 | from copy import deepcopy 6 | from easydict import EasyDict 7 | import torch 8 | 9 | class AIAgent: 10 | 11 | def __init__(self, team_name, player_names): 12 | cfg = EasyDict({ 13 | 'team_name': team_name, 14 | 'player_names': player_names, 15 | 'env': { 16 | 'name': 'gobigger', 17 | 'player_num_per_team': 2, 18 | 'team_num': 2, 19 | 'step_mul': 8 20 | }, 21 | 'agent': { 22 | 'player_id': None, 23 | 'game_player_id': None, 24 | 'features': {} 25 | }, 26 | 'checkpoint_path': 'PATH/MODEL_NAME.pth.tar' 27 | }) 28 | self.agents = {} 29 | for player_name in player_names: 30 | cfg_cp = deepcopy(cfg) 31 | cfg_cp.agent.player_id = player_name 32 | cfg_cp.agent.game_player_id = player_name 33 | agent = Agent(cfg_cp) 34 | agent.reset() 35 | agent.model.load_state_dict(torch.load(cfg.checkpoint_path, map_location='cpu')['model'], strict=False) 36 | self.agents[player_name] = agent 37 | 38 | def get_actions(self, obs): 39 | global_state, player_states = obs 40 | actions = {} 41 | for player_name, agent in self.agents.items(): 42 | action = agent.step([global_state, {player_name: player_states[player_name]}]) 43 | actions.update(action) 44 | return actions 45 | 46 | class Agent: 47 | 48 | def __init__(self, cfg,): 49 | self.whole_cfg = cfg 50 | self.player_num = self.whole_cfg.env.player_num_per_team 51 | self.team_num = self.whole_cfg.env.team_num 52 | self.game_player_id = self.whole_cfg.agent.game_player_id # start from 0 53 | self.game_team_id = self.game_player_id // self.player_num # start from 0 54 | self.player_id = self.whole_cfg.agent.player_id 55 | self.features = Features(self.whole_cfg) 56 | self.eval_padding = self.whole_cfg.agent.get('eval_padding', False) 57 | self.use_action_mask = self.whole_cfg.agent.get('use_action_mask', False) 58 | self.model = Model(self.whole_cfg) 59 | 60 | def reset(self): 61 | self.last_action_type = self.features.direction_num * 2 62 | 63 | def preprocess(self, obs): 64 | self.last_player_score = obs[1][self.game_player_id]['score'] 65 | if self.use_action_mask: 66 | can_eject = obs[1][self.game_player_id]['can_eject'] 67 | can_split = obs[1][self.game_player_id]['can_split'] 68 | action_mask = self.features.generate_action_mask(can_eject=can_eject,can_split=can_split) 69 | else: 70 | action_mask = self.features.generate_action_mask(can_eject=True,can_split=True) 71 | obs = self.features.transform_obs(obs, game_player_id=self.game_player_id, 72 | last_action_type=self.last_action_type,padding=self.eval_padding) 73 | obs = default_collate_with_dim([obs]) 74 | 75 | obs['action_mask'] = action_mask.unsqueeze(0) 76 | return obs 77 | 78 | def step(self, obs): 79 | self.raw_obs = obs 80 | obs = self.preprocess(obs) 81 | self.model_input = obs 82 | with torch.no_grad(): 83 | self.model_output = self.model.compute_action(self.model_input) 84 | actions = self.postprocess(self.model_output['action'].detach().numpy()) 85 | return actions 86 | 87 | def postprocess(self, model_actions): 88 | actions = {} 89 | actions[self.game_player_id] = self.features.transform_action(model_actions[0]) 90 | self.last_action_type = model_actions[0].item() 91 | return actions 92 | -------------------------------------------------------------------------------- /practice/cooperative_agent/model.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import torch.nn as nn 4 | from ..tools.util import read_config, deep_merge_dicts 5 | from ..tools.encoder import Encoder 6 | from ..tools.head import PolicyHead, ValueHead 7 | 8 | default_config = read_config(os.path.join(os.path.dirname(__file__), 'default_model_config.yaml')) 9 | 10 | class Model(nn.Module): 11 | def __init__(self, cfg={}, use_value_network=False): 12 | super(Model, self).__init__() 13 | self.whole_cfg = deep_merge_dicts(default_config, cfg) 14 | self.model_cfg = self.whole_cfg.model 15 | self.use_value_network = use_value_network 16 | self.encoder = Encoder(self.whole_cfg) 17 | self.policy_head = PolicyHead(self.whole_cfg) 18 | self.temperature = self.whole_cfg.agent.get('temperature', 1) 19 | 20 | # used in rl_eval actor 21 | def compute_action(self, obs, ): 22 | action_mask = obs.pop('action_mask',None) 23 | embedding = self.encoder(obs, ) 24 | logit = self.policy_head(embedding, temperature=self.temperature) 25 | if action_mask is not None: 26 | logit.masked_fill_(mask=action_mask,value=-1e9) 27 | dist = torch.distributions.Categorical(logits=logit) 28 | action = dist.sample() 29 | return {'action': action, 'logit': logit} -------------------------------------------------------------------------------- /practice/solo_agent/default_model_config.yaml: -------------------------------------------------------------------------------- 1 | var1: &VIEW_BINARY_NUM 8 2 | var2: &ABS_VIEW_BINARY_NUM 7 3 | agent: 4 | enable_baselines: [ 'score', 'spore','team_spore', 'clone','team_clone','opponent','team_opponent' ] 5 | features: 6 | max_ball_num: 64 7 | max_food_num: 256 8 | max_spore_num: 128 9 | direction_num: 12 10 | spatial_x: 64 11 | spatial_y: 64 12 | model: 13 | scalar_encoder: 14 | modules: 15 | view_x: 16 | arc: sign_binary 17 | num_embeddings: *ABS_VIEW_BINARY_NUM 18 | embedding_dim: 8 19 | view_y: 20 | arc: sign_binary 21 | num_embeddings: *ABS_VIEW_BINARY_NUM 22 | embedding_dim: 8 23 | view_width: 24 | arc: binary 25 | num_embeddings: *ABS_VIEW_BINARY_NUM 26 | embedding_dim: 8 27 | # view_height: 28 | # arc: binary 29 | # num_embeddings: *ABS_VIEW_BINARY_NUM 30 | # embedding_dim: 8 31 | score: 32 | arc: one_hot 33 | num_embeddings: 10 34 | embedding_dim: 8 35 | team_score: 36 | arc: one_hot 37 | num_embeddings: 10 38 | embedding_dim: 8 39 | rank: 40 | arc: one_hot 41 | num_embeddings: 4 42 | embedding_dim: 8 43 | time: 44 | arc: time 45 | embedding_dim: 8 46 | last_action_type: 47 | arc: one_hot 48 | num_embeddings: 27 # direction_num * 2 + 3 49 | input_dim: 80 50 | hidden_dim: 64 51 | layer_num: 2 52 | norm_type: 'none' 53 | output_dim: 32 54 | activation: 'relu' 55 | team_encoder: 56 | modules: 57 | alliance: 58 | arc: one_hot 59 | num_embeddings: 2 60 | view_x: 61 | arc: sign_binary 62 | num_embeddings: *ABS_VIEW_BINARY_NUM 63 | view_y: 64 | arc: sign_binary 65 | num_embeddings: *ABS_VIEW_BINARY_NUM 66 | # view_width: 67 | # arc: binary 68 | # num_embeddings: *ABS_VIEW_BINARY_NUM 69 | # embedding_dim: 12 70 | # view_height: 71 | # arc: binary 72 | # num_embeddings: *ABS_VIEW_BINARY_NUM 73 | # embedding_dim: 12 74 | # score: 75 | # arc: one_hot 76 | # num_embeddings: 10 77 | # embedding_dim: 12 78 | # team_score: 79 | # arc: one_hot 80 | # num_embeddings: 10 81 | # embedding_dim: 12 82 | # team_rank: 83 | # arc: one_hot 84 | # num_embeddings: 10 85 | # embedding_dim: 12 86 | embedding_dim: 16 87 | encoder: 88 | input_dim: 16 89 | hidden_dim: 32 90 | layer_num: 2 91 | activation: 'relu' 92 | norm_type: 'LN' 93 | transformer: 94 | head_num: 4 95 | ffn_size: 32 96 | layer_num: 2 97 | activation: 'relu' 98 | variant: 'postnorm' 99 | output: 100 | output_dim: 16 101 | activation: 'relu' 102 | norm_type: 'LN' 103 | ball_encoder: 104 | modules: 105 | alliance: 106 | arc: one_hot 107 | num_embeddings: 4 108 | score: 109 | arc: one_hot 110 | num_embeddings: 50 111 | radius: 112 | arc: unsqueeze 113 | # score_ratio: 114 | # arc: one_hot 115 | # num_embeddings: 50 116 | rank: 117 | arc: one_hot 118 | num_embeddings: 5 119 | x: 120 | arc: sign_binary 121 | num_embeddings: *VIEW_BINARY_NUM 122 | embedding_dim: 8 123 | y: 124 | arc: sign_binary 125 | num_embeddings: *VIEW_BINARY_NUM 126 | embedding_dim: 8 127 | next_x: 128 | arc: sign_binary 129 | num_embeddings: *VIEW_BINARY_NUM 130 | embedding_dim: 8 131 | next_y: 132 | arc: sign_binary 133 | num_embeddings: *VIEW_BINARY_NUM 134 | embedding_dim: 8 135 | embedding_dim: 64 136 | encoder: 137 | input_dim: 92 138 | hidden_dim: 128 139 | layer_num: 2 140 | activation: 'relu' 141 | norm_type: 'LN' 142 | transformer: 143 | head_num: 4 144 | ffn_size: 64 145 | layer_num: 3 146 | activation: 'relu' 147 | variant: 'postnorm' 148 | output: 149 | output_dim: 64 150 | activation: 'relu' 151 | norm_type: 'LN' 152 | spatial_encoder: 153 | scatter: 154 | input_dim: 64 155 | output_dim: 16 156 | scatter_type: add 157 | activation: 'relu' 158 | norm_type: 'LN' 159 | resnet: 160 | project_dim: 12 161 | down_channels: [32, 32, 16 ] 162 | activation: 'relu' 163 | norm_type: 'LN' 164 | output: 165 | output_dim: 64 166 | activation: 'relu' 167 | norm_type: 'LN' 168 | policy: 169 | embedding_dim: 64 170 | project: 171 | input_dim: 176 # scalar + team + ball + spatial 172 | activation: 'relu' 173 | norm_type: 'LN' 174 | resnet: 175 | activation: 'relu' 176 | norm_type: 'LN' 177 | res_num: 3 178 | value: 179 | embedding_dim: 64 180 | project: 181 | input_dim: 352 # scalar + team + ball + spatial 182 | activation: 'relu' 183 | norm_type: 'LN' 184 | resnet: 185 | activation: 'relu' 186 | norm_type: 'LN' 187 | res_num: 3 -------------------------------------------------------------------------------- /practice/test_ai.py: -------------------------------------------------------------------------------- 1 | from gobigger.envs import create_env 2 | from cooperative_agent.agent import AIAgent as AI 3 | 4 | env = create_env('st_t2p2') 5 | obs = env.reset() 6 | 7 | agent1 = AI(team_name=0, player_names=[0,1]) 8 | agent2 = AI(team_name=1, player_names=[2,3]) 9 | 10 | for i in range(1000): 11 | actions1 = agent1.get_actions(obs) 12 | actions2 = agent2.get_actions(obs) 13 | actions1.update(actions2) 14 | obs, rew, done, info = env.step(actions1) 15 | print('[{}] leaderboard={}'.format(i, obs[0]['leaderboard'])) 16 | if done: 17 | print('finish game!') 18 | break 19 | env.close() -------------------------------------------------------------------------------- /practice/tools/head.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | 3 | from .network.nn_module import fc_block 4 | from .network.res_block import ResFCBlock 5 | 6 | 7 | class PolicyHead(nn.Module): 8 | def __init__(self, cfg): 9 | super(PolicyHead, self).__init__() 10 | self.whole_cfg = cfg 11 | self.cfg = self.whole_cfg.model.policy 12 | 13 | self.embedding_dim = self.cfg.embedding_dim 14 | self.project_cfg = self.cfg.project 15 | self.project = fc_block(in_channels=self.project_cfg.input_dim, 16 | out_channels=self.embedding_dim, 17 | activation= self.project_cfg.activation, 18 | norm_type=self.project_cfg.norm_type) 19 | 20 | self.resnet_cfg = self.cfg.resnet 21 | blocks = [ResFCBlock(in_channels=self.embedding_dim, 22 | activation=self.resnet_cfg.activation, 23 | norm_type=self.resnet_cfg.norm_type) 24 | for _ in range(self.resnet_cfg.res_num)] 25 | self.resnet = nn.Sequential(*blocks) 26 | 27 | self.direction_num = self.whole_cfg.agent.features.get('direction_num', 12) 28 | self.action_num = 2 * self.direction_num + 3 29 | self.output_layer = fc_block(in_channels=self.embedding_dim, 30 | out_channels=self.action_num, 31 | norm_type=None, 32 | activation=None) 33 | 34 | def forward(self, x, temperature=1): 35 | x = self.project(x) 36 | x = self.resnet(x) 37 | logit = self.output_layer(x) 38 | logit /= temperature 39 | return logit 40 | 41 | 42 | class ValueHead(nn.Module): 43 | def __init__(self, cfg): 44 | super(ValueHead, self).__init__() 45 | self.whole_cfg = cfg 46 | self.cfg = self.whole_cfg.model.value 47 | 48 | self.embedding_dim = self.cfg.embedding_dim 49 | self.project_cfg = self.cfg.project 50 | self.project = fc_block(in_channels=self.project_cfg.input_dim, 51 | out_channels=self.embedding_dim, 52 | activation= self.project_cfg.activation, 53 | norm_type=self.project_cfg.norm_type) 54 | 55 | self.resnet_cfg = self.cfg.resnet 56 | blocks = [ResFCBlock(in_channels=self.embedding_dim, 57 | activation=self.resnet_cfg.activation, 58 | norm_type=self.resnet_cfg.norm_type) 59 | for _ in range(self.resnet_cfg.res_num)] 60 | self.resnet = nn.Sequential(*blocks) 61 | 62 | self.output_layer = fc_block(in_channels=self.embedding_dim, 63 | out_channels=1, 64 | norm_type=None, 65 | activation=None) 66 | def forward(self, x): 67 | x = self.project(x) 68 | x = self.resnet(x) 69 | x = self.output_layer(x) 70 | x = x.squeeze(1) 71 | return x -------------------------------------------------------------------------------- /practice/tools/network/__init__.py: -------------------------------------------------------------------------------- 1 | from .activation import build_activation 2 | from .res_block import ResBlock, ResFCBlock,ResFCBlock2 3 | from .nn_module import fc_block, fc_block2, conv2d_block, MLP 4 | from .normalization import build_normalization 5 | from .rnn import get_lstm, sequence_mask 6 | from .soft_argmax import SoftArgmax 7 | from .transformer import Transformer 8 | from .scatter_connection import ScatterConnection 9 | -------------------------------------------------------------------------------- /practice/tools/network/activation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Sensetime X-lab. All Rights Reserved 3 | 4 | Main Function: 5 | 1. build activation: you can use build_activation to build relu or glu 6 | """ 7 | import torch 8 | import torch.nn as nn 9 | 10 | 11 | class GLU(nn.Module): 12 | r""" 13 | Overview: 14 | Gating Linear Unit. 15 | This class does a thing like this: 16 | 17 | .. code:: python 18 | 19 | # Inputs: input, context, output_size 20 | # The gate value is a learnt function of the input. 21 | gate = sigmoid(linear(input.size)(context)) 22 | # Gate the input and return an output of desired size. 23 | gated_input = gate * input 24 | output = linear(output_size)(gated_input) 25 | return output 26 | Interfaces: 27 | forward 28 | 29 | .. tip:: 30 | 31 | This module also supports 2D convolution, in which case, the input and context must have the same shape. 32 | """ 33 | 34 | def __init__(self, input_dim: int, output_dim: int, context_dim: int, input_type: str = 'fc') -> None: 35 | r""" 36 | Overview: 37 | Init GLU 38 | Arguments: 39 | - input_dim (:obj:`int`): the input dimension 40 | - output_dim (:obj:`int`): the output dimension 41 | - context_dim (:obj:`int`): the context dimension 42 | - input_type (:obj:`str`): the type of input, now support ['fc', 'conv2d'] 43 | """ 44 | super(GLU, self).__init__() 45 | assert (input_type in ['fc', 'conv2d']) 46 | if input_type == 'fc': 47 | self.layer1 = nn.Linear(context_dim, input_dim) 48 | self.layer2 = nn.Linear(input_dim, output_dim) 49 | elif input_type == 'conv2d': 50 | self.layer1 = nn.Conv2d(context_dim, input_dim, 1, 1, 0) 51 | self.layer2 = nn.Conv2d(input_dim, output_dim, 1, 1, 0) 52 | 53 | def forward(self, x: torch.Tensor, context: torch.Tensor) -> torch.Tensor: 54 | r""" 55 | Overview: 56 | Return GLU computed tensor 57 | Arguments: 58 | - x (:obj:`torch.Tensor`) : the input tensor 59 | - context (:obj:`torch.Tensor`) : the context tensor 60 | Returns: 61 | - x (:obj:`torch.Tensor`): the computed tensor 62 | """ 63 | gate = self.layer1(context) 64 | gate = torch.sigmoid(gate) 65 | x = gate * x 66 | x = self.layer2(x) 67 | return x 68 | 69 | class Swish(nn.Module): 70 | 71 | def __init__(self): 72 | super(Swish, self).__init__() 73 | 74 | def forward(self, x): 75 | x = x * torch.sigmoid(x) 76 | return x 77 | 78 | def build_activation(activation: str, inplace: bool = None) -> nn.Module: 79 | r""" 80 | Overview: 81 | Return the activation module according to the given type. 82 | Arguments: 83 | - actvation (:obj:`str`): the type of activation module, now supports ['relu', 'glu', 'prelu'] 84 | - inplace (:obj:`bool`): can optionally do the operation in-place in relu. Default ``None`` 85 | Returns: 86 | - act_func (:obj:`nn.module`): the corresponding activation module 87 | """ 88 | if inplace is not None: 89 | assert activation == 'relu', 'inplace argument is not compatible with {}'.format(activation) 90 | else: 91 | inplace = True 92 | act_func = {'relu': nn.ReLU(inplace=inplace), 'glu': GLU, 'prelu': nn.PReLU(),'swish': Swish()} 93 | if activation in act_func.keys(): 94 | return act_func[activation] 95 | else: 96 | raise KeyError("invalid key for activation: {}".format(activation)) 97 | -------------------------------------------------------------------------------- /practice/tools/network/normalization.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import torch.nn as nn 3 | 4 | 5 | def build_normalization(norm_type: str, dim: Optional[int] = None) -> nn.Module: 6 | r""" 7 | Overview: 8 | Build the corresponding normalization module 9 | Arguments: 10 | - norm_type (:obj:`str`): type of the normaliztion, now support ['BN', 'IN', 'SyncBN', 'AdaptiveIN'] 11 | - dim (:obj:`int`): dimension of the normalization, when norm_type is in [BN, IN] 12 | Returns: 13 | - norm_func (:obj:`nn.Module`): the corresponding batch normalization function 14 | 15 | .. note:: 16 | For beginers, you can refer to to learn more about batch normalization. 17 | """ 18 | if dim is None: 19 | key = norm_type 20 | else: 21 | if norm_type in ['BN', 'IN', 'SyncBN']: 22 | key = norm_type + str(dim) 23 | elif norm_type in ['LN']: 24 | key = norm_type 25 | else: 26 | raise NotImplementedError("not support indicated dim when creates {}".format(norm_type)) 27 | norm_func = { 28 | 'BN1': nn.BatchNorm1d, 29 | 'BN2': nn.BatchNorm2d, 30 | 'LN': nn.LayerNorm, 31 | 'IN2': nn.InstanceNorm2d, 32 | } 33 | if key in norm_func.keys(): 34 | return norm_func[key] 35 | else: 36 | raise KeyError("invalid norm type: {}".format(key)) -------------------------------------------------------------------------------- /practice/tools/network/soft_argmax.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Sensetime X-lab. All Rights Reserved 3 | 4 | Main Function: 5 | 1. SoftArgmax: a nn.Module that computes SoftArgmax 6 | """ 7 | import torch 8 | import torch.nn as nn 9 | import torch.nn.functional as F 10 | 11 | 12 | class SoftArgmax(nn.Module): 13 | r""" 14 | Overview: 15 | a nn.Module that computes SoftArgmax 16 | 17 | Note: 18 | for more softargmax info, you can reference the wiki page 19 | or reference the lecture 20 | 21 | 22 | Interface: 23 | __init__, forward 24 | """ 25 | 26 | def __init__(self): 27 | r""" 28 | Overview: 29 | initialize the SoftArgmax module 30 | """ 31 | super(SoftArgmax, self).__init__() 32 | 33 | def forward(self, x): 34 | r""" 35 | Overview: 36 | soft-argmax for location regression 37 | 38 | Arguments: 39 | - x (:obj:`Tensor`): predict heat map 40 | 41 | Returns: 42 | - location (:obj:`Tensor`): predict location 43 | 44 | Shapes: 45 | - x (:obj:`Tensor`): :math:`(B, C, H, W)`, while B is the batch size, 46 | C is number of channels , H and W stands for height and width 47 | - location (:obj:`Tensor`): :math:`(B, 2)`, while B is the batch size 48 | """ 49 | B, C, H, W = x.shape 50 | device, dtype = x.device, x.dtype 51 | # 1 channel 52 | assert (x.shape[1] == 1) 53 | h_kernel = torch.arange(0, H, device=device).to(dtype) 54 | h_kernel = h_kernel.view(1, 1, H, 1).repeat(1, 1, 1, W) 55 | w_kernel = torch.arange(0, W, device=device).to(dtype) 56 | w_kernel = w_kernel.view(1, 1, 1, W).repeat(1, 1, H, 1) 57 | x = F.softmax(x.view(B, C, -1), dim=-1).view(B, C, H, W) 58 | h = (x * h_kernel).sum(dim=[1, 2, 3]) 59 | w = (x * w_kernel).sum(dim=[1, 2, 3]) 60 | return torch.stack([h, w], dim=1) 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | 5 | from setuptools import setup 6 | 7 | 8 | setup( 9 | name='gobigger', 10 | version='0.2.0', 11 | description='Go-Bigger: Multi-Agent Decision Intelligence Environment', 12 | author='OpenDILab', 13 | license='Apache License, Version 2.0', 14 | keywords='Go-Bigger DI', 15 | packages=[ 16 | 'gobigger', 17 | 'gobigger.agents', 18 | 'gobigger.balls', 19 | 'gobigger.server', 20 | 'gobigger.utils', 21 | 'gobigger.managers', 22 | 'gobigger.players', 23 | 'gobigger.render', 24 | 'gobigger.envs', 25 | 'gobigger.bin', 26 | 'gobigger.configs', 27 | 'gobigger.playbacks', 28 | ], 29 | install_requires=[ 30 | 'easydict', 31 | 'gym>=0.15.3', # pypy incompatible 32 | 'pygame>=2.0.0', 33 | 'pytest>=5.0.0', 34 | 'opencv-python', 35 | 'numpy>=1.10', 36 | 'numexpr', 37 | 'lz4', 38 | ] 39 | ) 40 | --------------------------------------------------------------------------------