diff --git a/README.md b/README.md index 0be1ec1..a250af1 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ ## Ongoing +* 2022.1.17 We updated our C++ based game engine implementation which is ten times faster than python version. Please check [cpp](https://github.com/opendilab/GoBigger/tree/cpp) branch to get more details. * 2021.11.13 We are holding a [competition](https://github.com/opendilab/GoBigger-Challenge-2021) —— Go-Bigger: Multi-Agent Decision Intelligence Environment. Come and make your agents in the game! GoBigger is a simple and efficient *agar-like* game engine and provides various interfaces for game AI development. The game is similar to [Agar](https://agar.io/), 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 when it reaches a sufficient size, allowing them to control multiple balls. diff --git a/docs/en/source/advanced/hyper.rst b/docs/en/source/advanced/hyper.rst new file mode 100644 index 0000000..60fb6b9 --- /dev/null +++ b/docs/en/source/advanced/hyper.rst @@ -0,0 +1,86 @@ +Derived Environments and Action Spaces +################ + +Overview +======================= + +On the basis of the GoBigger environment, considering the complexity of large-scale environment training and +algorithm verification for specific tasks, we provide some small environments under specific conditions, +as well as advanced serialization actions to facilitate training and algorithm design. + + +small environment +======================= + +We provide the initialization method of the small environment by providing config. + + +* The clone fits the ball, the clone holds the ball (2f2s) + + There are only two teams in this small environment, and there are two players in each team. +There will also be food summed thorn balls in the same scale in the map. In this map, the blue A ball +only needs to split to the right, while the blue B ball needs to eat the small ball split by the blue A ball +and move down, and eat the yellow A by splitting (or moving). ball. + + .. only:: html + + .. figure:: images/2f2s.png + :width: 300 + :align: center + + +* Measured increase (2f2s_v2) + + This small environment is the v2 version of 2f2s. In this map, the blue A ball only needs to split +to the right, while the blue B ball remains stationary to eat the yellow A ball. + + .. only:: html + + .. figure:: images/2f2s_v2.png + :width: 300 + :align: center + + +* Measured increase (2f2s_v3) + + This small environment is the v3 version of 2f2s. In this map, the blue A ball needs to split between +the yellow ball and the blue B ball and spore the center, while the blue B ball moves down to eat the yellow A ball. + + .. only:: html + + .. figure:: images/2f2s_v3.png + :width: 300 + :align: center + + +Advanced Serialization Actions +======================= + +Please check ``gobigger/hyper/tests`` to get more details on serialization actions. + + +* Straight line + + .. only:: html + + .. figure:: images/straight_merge.gif + :width: 362 + :align: center + + +* Four point ball + + .. only:: html + + .. figure:: images/quarter_merge.gif + :width: 362 + :align: center + + +* Eights + + .. only:: html + + .. figure:: images/eighth_merge.gif + :width: 338 + :align: center diff --git a/docs/en/source/advanced/images/2f2s.png b/docs/en/source/advanced/images/2f2s.png new file mode 100644 index 0000000..d63ab69 Binary files /dev/null and b/docs/en/source/advanced/images/2f2s.png differ diff --git a/docs/en/source/advanced/images/2f2s_v2.png b/docs/en/source/advanced/images/2f2s_v2.png new file mode 100644 index 0000000..97188a2 Binary files /dev/null and b/docs/en/source/advanced/images/2f2s_v2.png differ diff --git a/docs/en/source/advanced/images/2f2s_v3.png b/docs/en/source/advanced/images/2f2s_v3.png new file mode 100644 index 0000000..922023f Binary files /dev/null and b/docs/en/source/advanced/images/2f2s_v3.png differ diff --git a/docs/en/source/advanced/images/eighth_merge.gif b/docs/en/source/advanced/images/eighth_merge.gif new file mode 100755 index 0000000..0dc3c2e Binary files /dev/null and b/docs/en/source/advanced/images/eighth_merge.gif differ diff --git a/docs/en/source/advanced/images/quarter_merge.gif b/docs/en/source/advanced/images/quarter_merge.gif new file mode 100755 index 0000000..b8f3d62 Binary files /dev/null and b/docs/en/source/advanced/images/quarter_merge.gif differ diff --git a/docs/en/source/advanced/images/straight_merge.gif b/docs/en/source/advanced/images/straight_merge.gif new file mode 100755 index 0000000..29b1895 Binary files /dev/null and b/docs/en/source/advanced/images/straight_merge.gif differ diff --git a/docs/en/source/index.rst b/docs/en/source/index.rst index f2b38fe..44f11d8 100644 --- a/docs/en/source/index.rst +++ b/docs/en/source/index.rst @@ -32,6 +32,7 @@ Indices and tables advanced/cfg_intro advanced/custom_init advanced/collision + advanced/hyper .. toctree:: :maxdepth: 2 diff --git a/docs/en/source/tutorial/quick_start.rst b/docs/en/source/tutorial/quick_start.rst index 9b5414e..27a89bb 100644 --- a/docs/en/source/tutorial/quick_start.rst +++ b/docs/en/source/tutorial/quick_start.rst @@ -15,7 +15,7 @@ After installation, you can launch your game environment easily according the fo server = Server() render = EnvRender(server.map_width, server.map_height) server.set_render(render) - server.start() + server.reset() player_names = server.get_player_names_with_team() # get [[team1_player1, team1_player2], [team2_player1, team2_player2], ...] for i in range(10000): diff --git a/docs/zh_CN/source/advanced/hyper.rst b/docs/zh_CN/source/advanced/hyper.rst new file mode 100644 index 0000000..448e776 --- /dev/null +++ b/docs/zh_CN/source/advanced/hyper.rst @@ -0,0 +1,82 @@ +衍生环境和动作空间 +################ + +总览 +====================== + +在 GoBigger 环境的基础上,考虑到大环境训练的复杂度以及针对特定任务的算法验证,我们提供了部分特定条件下的小环境,以及高级序列化动作来方便 +大家进行训练和算法设计。 + + +小环境 +====================== + +我们通过提供config的方式来提供小环境的初始化方式。 + + +* 分身合球,分身持球(2f2s) + + 这个小环境中只有两只队伍,每支队伍中有两个玩家。地图中同时还会存在相同比例的食物求和荆棘球。在本地图中,蓝色A球 + 只需要向右边进行分裂,同时蓝色B球需要吃掉蓝色A球分裂出来的小球并向下移动,通过分裂(或移动)的方式吃掉黄色A球。 + + .. only:: html + + .. figure:: images/2f2s.png + :width: 300 + :align: center + + +* 测涨(2f2s_v2) + + 这个小环境是2f2s的v2版本。在本地图中,蓝色A球只需要向右边进行分裂,同时蓝色B球保持不动即可吃掉黄色A球。 + + .. only:: html + + .. figure:: images/2f2s_v2.png + :width: 300 + :align: center + + +* 测涨(2f2s_v3) + + 这个小环境是2f2s的v3版本。在本地图中,蓝色A球需要向黄色球和蓝色B球之间进行分裂并向中心吐孢子,同时蓝色B球 + 向下移动即可吃掉黄色A球。 + + .. only:: html + + .. figure:: images/2f2s_v3.png + :width: 300 + :align: center + + +高级序列化动作 +====================== + +请查看 ``gobigger/hyper/tests`` 目录下的测试文件来使用高级序列化动作。 + + +* 直线合球 + + .. only:: html + + .. figure:: images/straight_merge.gif + :width: 362 + :align: center + + +* 四分合球 + + .. only:: html + + .. figure:: images/quarter_merge.gif + :width: 362 + :align: center + + +* 八分合球 + + .. only:: html + + .. figure:: images/eighth_merge.gif + :width: 338 + :align: center diff --git a/docs/zh_CN/source/advanced/images/2f2s.png b/docs/zh_CN/source/advanced/images/2f2s.png new file mode 100644 index 0000000..d63ab69 Binary files /dev/null and b/docs/zh_CN/source/advanced/images/2f2s.png differ diff --git a/docs/zh_CN/source/advanced/images/2f2s_v2.png b/docs/zh_CN/source/advanced/images/2f2s_v2.png new file mode 100644 index 0000000..97188a2 Binary files /dev/null and b/docs/zh_CN/source/advanced/images/2f2s_v2.png differ diff --git a/docs/zh_CN/source/advanced/images/2f2s_v3.png b/docs/zh_CN/source/advanced/images/2f2s_v3.png new file mode 100644 index 0000000..922023f Binary files /dev/null and b/docs/zh_CN/source/advanced/images/2f2s_v3.png differ diff --git a/docs/zh_CN/source/advanced/images/eighth_merge.gif b/docs/zh_CN/source/advanced/images/eighth_merge.gif new file mode 100755 index 0000000..0dc3c2e Binary files /dev/null and b/docs/zh_CN/source/advanced/images/eighth_merge.gif differ diff --git a/docs/zh_CN/source/advanced/images/quarter_merge.gif b/docs/zh_CN/source/advanced/images/quarter_merge.gif new file mode 100755 index 0000000..b8f3d62 Binary files /dev/null and b/docs/zh_CN/source/advanced/images/quarter_merge.gif differ diff --git a/docs/zh_CN/source/advanced/images/straight_merge.gif b/docs/zh_CN/source/advanced/images/straight_merge.gif new file mode 100755 index 0000000..29b1895 Binary files /dev/null and b/docs/zh_CN/source/advanced/images/straight_merge.gif differ diff --git a/docs/zh_CN/source/index.rst b/docs/zh_CN/source/index.rst index 659c73c..a72d149 100644 --- a/docs/zh_CN/source/index.rst +++ b/docs/zh_CN/source/index.rst @@ -35,6 +35,7 @@ GoBigger 提供了多种接口供用户方便快捷地与游戏环境进行交 advanced/cfg_intro advanced/custom_init advanced/collision + advanced/hyper .. toctree:: :maxdepth: 2 diff --git a/docs/zh_CN/source/tutorial/quick_start.rst b/docs/zh_CN/source/tutorial/quick_start.rst index e65d028..9a4980b 100644 --- a/docs/zh_CN/source/tutorial/quick_start.rst +++ b/docs/zh_CN/source/tutorial/quick_start.rst @@ -15,7 +15,7 @@ server = Server() render = EnvRender(server.map_width, server.map_height) server.set_render(render) - server.start() + server.reset() player_names = server.get_player_names_with_team() # get [[team1_player1, team1_player2], [team2_player1, team2_player2], ...] for i in range(10000): diff --git a/docs/zh_CN/source/tutorial/space.rst b/docs/zh_CN/source/tutorial/space.rst index cdf4d1c..71745a6 100644 --- a/docs/zh_CN/source/tutorial/space.rst +++ b/docs/zh_CN/source/tutorial/space.rst @@ -84,7 +84,7 @@ } } -``player_state`` 中的 ``overlap`` 代表的是当前玩家视野中出现的球的结构化信息。``overlap`` 是一个简单的字典,每个键值对代表了视野中的一种球的信息。``overlap`` 中包含了食物球,荆棘球,孢子球,分身球的结构化信息(位置和半径,如果是分身球则包含所属玩家名称和队伍名称)。具体来说,例如我们发现 ``food`` 字段的内容为 ``[[3.0, 4.0, 2], ..]``(简单起见这里只展示了列表中的第一个元素),那么其中的含义是玩家的视野中,坐标 ``(3.0, 4.0)`` 位置存在一个半径为 ``2`` 的食物球。 +``player_state`` 中的 ``overlap`` 代表的是当前玩家视野中出现的球的结构化信息。``overlap`` 是一个简单的字典,每个键值对代表了视野中的一种球的信息。``overlap`` 中包含了食物球,荆棘球,孢子球,分身球的结构化信息(位置和半径,如果是分身球则包含所属玩家名称和队伍名称)。具体来说,例如我们发现 ``food`` 字段的内容为 ``[[3.0, 4.0, 2], ..]`` (简单起见这里只展示了列表中的第一个元素),那么其中的含义是玩家的视野中,坐标 ``(3.0, 4.0)`` 位置存在一个半径为 ``2`` 的食物球。 请注意,每一种球的信息列表的长度是不确定的。例如,在当前帧视野中一共有20个食物球,那么当前 ``food`` 对应的列表长度为20。在下一帧,视野内的食物球如果变为25,则对应的列表长度将会变成25。 此外,如果某个球只有一部分出现在玩家视野中,GoBigger也会在 ``overlap`` 中给出该球的圆心和半径信息。 diff --git a/gobigger/agents/base_agent.py b/gobigger/agents/base_agent.py index e5270f6..dae8fa3 100644 --- a/gobigger/agents/base_agent.py +++ b/gobigger/agents/base_agent.py @@ -1,5 +1,4 @@ import os -import random import logging diff --git a/gobigger/balls/clone_ball.py b/gobigger/balls/clone_ball.py index 257b20f..fb5bb32 100644 --- a/gobigger/balls/clone_ball.py +++ b/gobigger/balls/clone_ball.py @@ -2,7 +2,6 @@ import logging import uuid import copy -import random from easydict import EasyDict from pygame.math import Vector2 @@ -92,7 +91,7 @@ def __init__(self, team_name, name, position, border, size=None, vel=None, acc=N if (self.vel + self.vel_last).length() != 0: self.direction = copy.deepcopy((self.vel + self.vel_last).normalize()) else: - self.direction = Vector2(random.random(), random.random()).normalize() + self.direction = Vector2(0.000001, 0.000001).normalize() self.check_border() self.stop_flag = stop_flag @@ -160,7 +159,7 @@ def move(self, given_acc=None, given_acc_center=None, duration=0.05): if (self.vel + self.vel_last).length() != 0: self.direction = copy.deepcopy((self.vel + self.vel_last).normalize()) else: - self.direction = Vector2(random.random(), random.random()).normalize() + self.direction = Vector2(0.000001, 0.000001).normalize() self.check_border() def eat(self, ball, clone_num=None): @@ -315,7 +314,7 @@ def stop(self, direction=None): self.stop_time = 0 self.acc_stop = - 1 / self.stop_zero_time * (self.vel + self.vel_last) self.acc = Vector2(0, 0) - self.direction = direction if direction is not None else Vector2(random.random(), random.random()) + self.direction = direction if direction is not None else Vector2(0.000001, 0.000001) self.last_given_acc = Vector2(0, 0) return True diff --git a/gobigger/bin/demo_bot.py b/gobigger/bin/demo_bot.py index 8a2f511..1137714 100644 --- a/gobigger/bin/demo_bot.py +++ b/gobigger/bin/demo_bot.py @@ -3,7 +3,6 @@ import uuid from pygame.math import Vector2 import time -import random import numpy as np import cv2 import pygame diff --git a/gobigger/bin/play.py b/gobigger/bin/play.py index 86fe137..c27760f 100644 --- a/gobigger/bin/play.py +++ b/gobigger/bin/play.py @@ -3,7 +3,6 @@ import uuid from pygame.math import Vector2 import pygame -import random import numpy as np import cv2 import argparse diff --git a/gobigger/bin/play_hyper.py b/gobigger/bin/play_hyper.py new file mode 100644 index 0000000..d0709b5 --- /dev/null +++ b/gobigger/bin/play_hyper.py @@ -0,0 +1,110 @@ +import logging +import pytest +import uuid +from pygame.math import Vector2 +import pygame +import numpy as np +import cv2 +import argparse +import time +import importlib + +from gobigger.utils import Border +from gobigger.server import Server +from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender +from gobigger.agents import BotAgent + +logging.basicConfig(level=logging.DEBUG) + + +def play_by_config(config_name): + config_module = importlib.import_module('gobigger.hyper.configs.config_{}'.format(config_name)) + config = config_module.server_default_config + server = Server(config) + server.reset() + render = RealtimeRender(server.map_width, server.map_height) + server.set_render(render) + human_team_name = '0' + human_team_player_name = [] + bot_agents = [] + for player in server.player_manager.get_players(): + if player.team_name != human_team_name: + bot_agents.append(BotAgent(player.name)) + else: + human_team_player_name.append(player.name) + fps_real = 0 + t1 = time.time() + clock = pygame.time.Clock() + fps_set = server.state_tick_per_second + for i in range(100000): + obs = server.obs() + # actions_bot = {bot_agent.name: bot_agent.step(obs[1][bot_agent.name]) for bot_agent in bot_agents} + actions_bot = {bot_agent.name: [None, None, -1] for bot_agent in bot_agents} + actions = {player_name: [None, None, -1] for player_name in human_team_player_name} + x, y = None, None + action_type = -1 + # ================ control by keyboard =============== + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + x1, y1, x2, y2 = None, None, None, None + action_type1 = -1 + action_type2 = -1 + if event.key == pygame.K_UP: + x1, y1 = 0, -1 + if event.key == pygame.K_DOWN: + x1, y1 = 0, 1 + if event.key == pygame.K_LEFT: + x1, y1 = -1, 0 + if event.key == pygame.K_RIGHT: + x1, y1 = 1, 0 + if event.key == pygame.K_LEFTBRACKET: # Spores + action_type1 = 0 + if event.key == pygame.K_RIGHTBRACKET: # Splite + action_type1 = 1 + if event.key == pygame.K_BACKSLASH: # Stop moving + action_type1 = 2 + if event.key == pygame.K_w: + x2, y2 = 0, -1 + if event.key == pygame.K_s: + x2, y2 = 0, 1 + if event.key == pygame.K_a: + x2, y2 = -1, 0 + if event.key == pygame.K_d: + x2, y2 = 1, 0 + if event.key == pygame.K_1: # Spores + action_type2 = 0 + if event.key == pygame.K_2: # Splite + action_type2 = 1 + if event.key == pygame.K_3: # Stop moving + action_type2 = 2 + actions = { + human_team_player_name[0]: [x1, y1, action_type1], + human_team_player_name[1]: [x2, y2, action_type2], + } + if server.last_time < server.match_time: + actions.update(actions_bot) + print(actions) + server.step_state_tick(actions=actions) + if actions is not None and x is not None and y is not None: + render.fill(server, direction=Vector2(x, y), fps=fps_real, last_time=server.last_time) + else: + render.fill(server, direction=None, fps=fps_real, last_time=server.last_time) + render.show() + if i % server.state_tick_per_second == 0: + t2 = time.time() + fps_real = server.state_tick_per_second/(t2-t1) + t1 = time.time() + else: + logging.debug('Game Over') + break + clock.tick(fps_set) + render.close() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', type=str, default='2f2s') + args = parser.parse_args() + + play_by_config(args.config) + \ No newline at end of file diff --git a/gobigger/hyper/__init__.py b/gobigger/hyper/__init__.py new file mode 100644 index 0000000..8ae0381 --- /dev/null +++ b/gobigger/hyper/__init__.py @@ -0,0 +1 @@ +from .actions import StraightMergeHyperAction, QuarterMergeHyperAction, EighthMergeHyperAction diff --git a/gobigger/hyper/actions.py b/gobigger/hyper/actions.py new file mode 100644 index 0000000..ef2d18c --- /dev/null +++ b/gobigger/hyper/actions.py @@ -0,0 +1,361 @@ +import os +import sys +import time +import pygame +from pygame.math import Vector2 +import queue +import math +from easydict import EasyDict + +from gobigger.server import Server +from gobigger.render import EnvRender, RealtimeRender + + +class HyperAction: + def __init__(self): + raise NotImplementedError + def update(self, obs): + raise NotImplementedError + def get(self): + raise NotImplementedError + + +class StraightMergeHyperAction(HyperAction): + + ######################################################### + # 直线中合 # + ######################################################### + + def __init__(self, player_name1, player_name2): + self.player_name1 = player_name1 + self.player_name2 = player_name2 + self.state = "reach" # in ['reach', 'merge'] + self.need_stop = 0 + + def update(self, obs1, obs2): + self.balls1 = self.get_balls_from_obs_by_name(self.player_name1, obs1) + self.balls2 = self.get_balls_from_obs_by_name(self.player_name2, obs2) + + def get(self): + if self.state == 'reach': + tmp = self.straight_merge_reach() + if tmp is not None: + return tmp + if self.state == 'action': + return self.straight_merge_action() + + def get_balls_from_obs_by_name(self, player_name, obs): + balls = [] + overlap = obs['overlap'] + for item in overlap['clone']: + if str(int(item[3])) == player_name: + ball = EasyDict() + ball.position = Vector2(float(item[0]), float(item[1])) + ball.radius = float(item[2]) + ball.player = str(int(item[3])) + ball.team = str(int(item[4])) + balls.append(ball) + return balls + + def straight_merge_reach(self): + """ + 移动到相近位置(相切) + """ + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if len(self.balls1) == 1 and len(self.balls2) == 1 and \ + (ball1.position - ball2.position).length() <= 1.5 * (ball1.radius + ball2.radius): + self.state = 'action' + return None + if len(self.balls1) != 1: + action1 = [None, None, 2] + else: + direction1 = ball2.position - ball1.position + action1 = [direction1.x, direction1.y, -1] + if len(self.balls2) != 1: + action2 = [None, None, 2] + else: + direction2 = ball1.position - ball2.position + action2 = [direction2.x, direction2.y, -1] + actions = { + str(ball1.player): action1, + str(ball2.player): action2, + } + return actions + + def straight_merge_action(self): + """ + 直线中合 + balls1是较大的球,balls2是较小的球 + 条件:1. 同队友都只有一个球。 + """ + ret = {} + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if self.need_stop > 0: + self.need_stop -= 1 + return { + ball1.player: [None, None, 2], + ball2.player: [None, None, 2], + } + size1 = ball1.radius ** 2 + size2 = ball2.radius ** 2 + split_size_min = 100 + if len(self.balls1) == 1: + tmp = ball2.position - ball1.position + direction = [tmp.x, tmp.y] + else: + direction = [None, None] + if size1 > split_size_min: + ret[ball1.player] = [*direction, 1] # split + ret[ball2.player] = [None, None, 2] # stop + self.need_stop = 5 + else: + ret = None + return ret + + +class QuarterMergeHyperAction(HyperAction): + + ######################################################### + # 四分中合 # + ######################################################### + + def __init__(self, player_name1, player_name2): + self.player_name1 = player_name1 + self.player_name2 = player_name2 + self.state = "reach" # in ['reach', 'merge'] + self.need_stop = 0 + self.split_count = 0 + + def update(self, obs1, obs2): + self.balls1 = self.get_balls_from_obs_by_name(self.player_name1, obs1) + self.balls2 = self.get_balls_from_obs_by_name(self.player_name2, obs2) + + def get(self): + if self.state == 'reach': + tmp = self.quarter_merge_reach() + if tmp is not None: + return tmp + if self.state == 'action': + return self.quarter_merge_action() + + def get_balls_from_obs_by_name(self, player_name, obs): + balls = [] + overlap = obs['overlap'] + for item in overlap['clone']: + if str(int(item[3])) == player_name: + ball = EasyDict() + ball.position = Vector2(float(item[0]), float(item[1])) + ball.radius = float(item[2]) + ball.player = str(int(item[3])) + ball.team = str(int(item[4])) + balls.append(ball) + return balls + + def quarter_merge_reach(self): + """ + 移动到相近位置(相切) + """ + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if len(self.balls1) == 1 and len(self.balls2) == 1 and \ + (ball1.position - ball2.position).length() <= 1.5 * (ball1.radius + ball2.radius): + self.state = 'action' + return None + if len(self.balls1) != 1: + action1 = [None, None, 2] + else: + direction1 = ball2.position - ball1.position + action1 = [direction1.x, direction1.y, -1] + if len(self.balls2) != 1: + action2 = [None, None, 2] + else: + direction2 = ball1.position - ball2.position + action2 = [direction2.x, direction2.y, -1] + actions = { + str(ball1.player): action1, + str(ball2.player): action2, + } + return actions + + def quarter_merge_action(self): + """ + 直线中合 + balls1是较大的球,balls2是较小的球 + 条件:1. 同队友都只有一个球。 + """ + def cal_centroid(balls): + ''' + Overview: + Calculate the centroid + ''' + x = 0 + y = 0 + total_size = 0 + for ball in balls: + x += ball.radius ** 2 * ball.position.x + y += ball.radius ** 2 * ball.position.y + total_size += ball.radius ** 2 + return Vector2(x, y) / total_size + + ret = {} + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if self.need_stop > 0: + self.need_stop -= 1 + return { + ball1.player: [None, None, 2], + ball2.player: [None, None, 2], + } + size1 = ball1.radius ** 2 + size2 = ball2.radius ** 2 + split_size_min = 100 + centroid = cal_centroid(self.balls1) + if len(self.balls1) == 1 or self.split_count == 0: + tmp = ball2.position - ball1.position + tmp = Vector2(tmp.x * math.cos(math.pi/4) - tmp.y * math.sin(math.pi/4), + tmp.x * math.sin(math.pi/4) + tmp.y * math.cos(math.pi/4)) + direction = [tmp.x, tmp.y] + self.split_count += 1 + elif self.split_count == 1: + tmp = ball2.position - centroid + direction = [tmp.x, tmp.y] + self.split_count += 1 + else: + direction = [None, None] + self.split_count += 1 + if size1 > split_size_min: + ret[ball1.player] = [*direction, 1] # split + tmp = centroid - ball2.position + ret[ball2.player] = [tmp.x, tmp.y, 2] # stop + self.need_stop = 5 + else: + ret = None + return ret + + +class EighthMergeHyperAction(HyperAction): + + ######################################################### + # 四分中合 # + ######################################################### + + def __init__(self, player_name1, player_name2): + self.player_name1 = player_name1 + self.player_name2 = player_name2 + self.state = "reach" # in ['reach', 'merge'] + self.need_stop = 0 + self.split_count = 0 + + def update(self, obs1, obs2): + self.balls1 = self.get_balls_from_obs_by_name(self.player_name1, obs1) + self.balls2 = self.get_balls_from_obs_by_name(self.player_name2, obs2) + + def get(self): + if self.state == 'reach': + tmp = self.quarter_merge_reach() + if tmp is not None: + return tmp + if self.state == 'action': + return self.quarter_merge_action() + + def get_balls_from_obs_by_name(self, player_name, obs): + balls = [] + overlap = obs['overlap'] + for item in overlap['clone']: + if str(int(item[3])) == player_name: + ball = EasyDict() + ball.position = Vector2(float(item[0]), float(item[1])) + ball.radius = float(item[2]) + ball.player = str(int(item[3])) + ball.team = str(int(item[4])) + balls.append(ball) + return balls + + def quarter_merge_reach(self): + """ + 移动到相近位置(相切) + """ + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if len(self.balls1) == 1 and len(self.balls2) == 1 and \ + (ball1.position - ball2.position).length() <= 1.5 * (ball1.radius + ball2.radius): + self.state = 'action' + return None + if len(self.balls1) != 1: + action1 = [None, None, 2] + else: + direction1 = ball2.position - ball1.position + action1 = [direction1.x, direction1.y, -1] + if len(self.balls2) != 1: + action2 = [None, None, 2] + else: + direction2 = ball1.position - ball2.position + action2 = [direction2.x, direction2.y, -1] + actions = { + str(ball1.player): action1, + str(ball2.player): action2, + } + return actions + + def quarter_merge_action(self): + """ + 直线中合 + balls1是较大的球,balls2是较小的球 + 条件:1. 同队友都只有一个球。 + """ + def cal_centroid(balls): + ''' + Overview: + Calculate the centroid + ''' + x = 0 + y = 0 + total_size = 0 + for ball in balls: + x += ball.radius ** 2 * ball.position.x + y += ball.radius ** 2 * ball.position.y + total_size += ball.radius ** 2 + return Vector2(x, y) / total_size + + ret = {} + ball1 = self.balls1[0] + ball2 = self.balls2[0] + if self.need_stop > 0: + self.need_stop -= 1 + return { + ball1.player: [None, None, 2], + ball2.player: [None, None, 2], + } + size1 = ball1.radius ** 2 + size2 = ball2.radius ** 2 + split_size_min = 100 + centroid = cal_centroid(self.balls1) + if len(self.balls1) == 1 or self.split_count == 0: + tmp = ball2.position - ball1.position + tmp = Vector2(tmp.x * math.cos(math.pi/4) - tmp.y * math.sin(math.pi/4), + tmp.x * math.sin(math.pi/4) + tmp.y * math.cos(math.pi/4)) + direction = [tmp.x, tmp.y] + self.split_count += 1 + elif self.split_count == 1: + tmp = ball2.position - centroid + direction = [tmp.x, tmp.y] + self.split_count += 1 + else: + direction = [None, None] + self.split_count += 1 + if size1 > split_size_min: + ret[ball1.player] = [*direction, 1] # split + tmp = centroid - ball2.position + ret[ball2.player] = [tmp.x, tmp.y, 2] # stop + self.need_stop = 5 + else: + tmp = ball2.position - centroid + ret = { + ball1.player: [tmp.x, tmp.y, -1], + ball2.player: [None, None, 2], + } + return ret + + diff --git a/gobigger/hyper/configs/__init__.py b/gobigger/hyper/configs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gobigger/hyper/configs/config_2f2s.py b/gobigger/hyper/configs/config_2f2s.py new file mode 100644 index 0000000..7268a93 --- /dev/null +++ b/gobigger/hyper/configs/config_2f2s.py @@ -0,0 +1,91 @@ +# 分身合球,分身吃球 +server_default_config = dict( + team_num=2, + player_num_per_team=2, + map_width=300, + map_height=300, + match_time=10, + state_tick_per_second=10, # frame + action_tick_per_second=5, # frame + collision_detection_type='precision', + save_video=False, + save_quality='high', # ['high', 'low'] + save_path='', + save_bin=False, # save bin to go-explore + load_bin=False, + load_bin_path='', + load_bin_frame_num = 'all', + jump_to_frame_file = '', + manager_settings=dict( + # food setting + food_manager=dict( + num_init=180, # initial number + num_min=180, # Minimum number + num_max=225, # Maximum number + refresh_time=2, # Time interval (seconds) for refreshing food in the map + refresh_num=0, # The number of refreshed foods in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=2, + radius_max=2, + ), + ), + # thorns setting + thorns_manager=dict( + num_init=1, # initial number + num_min=1, # Minimum number + num_max=2, # Maximum number + refresh_time=6, # Time interval (seconds) for refreshing thorns in the map + refresh_num=0, # The number of refreshed thorns in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=12, + radius_max=20, + vel_max=100, + eat_spore_vel_init=10, + eat_spore_vel_zero_time=1, + ) + ), + # player setting + player_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + acc_max=100, + vel_max=25, + radius_min=3, + radius_max=300, + radius_init=3, + part_num_max=16, + on_thorns_part_num=10, + on_thorns_part_radius_max=20, + split_radius_min=10, + eject_radius_min=10, + recombine_age=20, + split_vel_init=30, + split_vel_zero_time=1, + stop_zero_time=1, + size_decay_rate=0.00005, + given_acc_weight=10, + ) + ), + # spore setting + spore_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=3, + radius_max=3, + vel_init=250, + vel_zero_time=0.3, + spore_radius_init=20, + ) + ) + ), + custom_init=dict( + food=[], # only position and radius + thorns=[[300, 300, 16]], # only position and radius + spore=[], # only position and radius + clone=[[80, 100, 16, '0', '0'], [130, 100, 10, '1', '0'], + [130, 130, 12, '2', '1'], [300, 300, 3, '3', '1']], + ), + obs_settings=dict( + with_spatial=True, + with_speed=False, + with_all_vision=False, + ), +) diff --git a/gobigger/hyper/configs/config_2f2s_v2.py b/gobigger/hyper/configs/config_2f2s_v2.py new file mode 100644 index 0000000..339ba3f --- /dev/null +++ b/gobigger/hyper/configs/config_2f2s_v2.py @@ -0,0 +1,91 @@ +# 侧涨 +server_default_config = dict( + team_num=2, + player_num_per_team=2, + map_width=300, + map_height=300, + match_time=10, + state_tick_per_second=10, # frame + action_tick_per_second=5, # frame + collision_detection_type='precision', + save_video=False, + save_quality='high', # ['high', 'low'] + save_path='', + save_bin=False, # save bin to go-explore + load_bin=False, + load_bin_path='', + load_bin_frame_num = 'all', + jump_to_frame_file = '', + manager_settings=dict( + # food setting + food_manager=dict( + num_init=180, # initial number + num_min=180, # Minimum number + num_max=225, # Maximum number + refresh_time=2, # Time interval (seconds) for refreshing food in the map + refresh_num=0, # The number of refreshed foods in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=2, + radius_max=2, + ), + ), + # thorns setting + thorns_manager=dict( + num_init=1, # initial number + num_min=1, # Minimum number + num_max=2, # Maximum number + refresh_time=6, # Time interval (seconds) for refreshing thorns in the map + refresh_num=0, # The number of refreshed thorns in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=12, + radius_max=20, + vel_max=100, + eat_spore_vel_init=10, + eat_spore_vel_zero_time=1, + ) + ), + # player setting + player_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + acc_max=100, + vel_max=25, + radius_min=3, + radius_max=300, + radius_init=3, + part_num_max=16, + on_thorns_part_num=10, + on_thorns_part_radius_max=20, + split_radius_min=10, + eject_radius_min=10, + recombine_age=20, + split_vel_init=30, + split_vel_zero_time=1, + stop_zero_time=1, + size_decay_rate=0.00005, + given_acc_weight=10, + ) + ), + # spore setting + spore_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=3, + radius_max=3, + vel_init=250, + vel_zero_time=0.3, + spore_radius_init=20, + ) + ) + ), + custom_init=dict( + food=[], # only position and radius + thorns=[[300, 300, 16]], # only position and radius + spore=[], # only position and radius + clone=[[80, 100, 16, '0', '0'], [130, 100, 10, '1', '0'], + [130, 115, 12, '2', '1'], [300, 300, 3, '3', '1']], + ), + obs_settings=dict( + with_spatial=True, + with_speed=False, + with_all_vision=False, + ), +) diff --git a/gobigger/hyper/configs/config_2f2s_v3.py b/gobigger/hyper/configs/config_2f2s_v3.py new file mode 100644 index 0000000..4558e64 --- /dev/null +++ b/gobigger/hyper/configs/config_2f2s_v3.py @@ -0,0 +1,91 @@ +# 母体 +server_default_config = dict( + team_num=2, + player_num_per_team=2, + map_width=300, + map_height=300, + match_time=10, + state_tick_per_second=10, # frame + action_tick_per_second=5, # frame + collision_detection_type='precision', + save_video=False, + save_quality='high', # ['high', 'low'] + save_path='', + save_bin=False, # save bin to go-explore + load_bin=False, + load_bin_path='', + load_bin_frame_num = 'all', + jump_to_frame_file = '', + manager_settings=dict( + # food setting + food_manager=dict( + num_init=180, # initial number + num_min=180, # Minimum number + num_max=225, # Maximum number + refresh_time=2, # Time interval (seconds) for refreshing food in the map + refresh_num=0, # The number of refreshed foods in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=2, + radius_max=2, + ), + ), + # thorns setting + thorns_manager=dict( + num_init=1, # initial number + num_min=1, # Minimum number + num_max=2, # Maximum number + refresh_time=6, # Time interval (seconds) for refreshing thorns in the map + refresh_num=0, # The number of refreshed thorns in the map each time + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=12, + radius_max=20, + vel_max=100, + eat_spore_vel_init=10, + eat_spore_vel_zero_time=1, + ) + ), + # player setting + player_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + acc_max=100, + vel_max=25, + radius_min=3, + radius_max=300, + radius_init=3, + part_num_max=16, + on_thorns_part_num=10, + on_thorns_part_radius_max=20, + split_radius_min=10, + eject_radius_min=10, + recombine_age=20, + split_vel_init=30, + split_vel_zero_time=1, + stop_zero_time=1, + size_decay_rate=0.00005, + given_acc_weight=10, + ) + ), + # spore setting + spore_manager=dict( + ball_settings=dict( # The specific parameter description can be viewed in the ball module + radius_min=3, + radius_max=3, + vel_init=250, + vel_zero_time=0.3, + spore_radius_init=20, + ) + ) + ), + custom_init=dict( + food=[], # only position and radius + thorns=[[300, 300, 16]], # only position and radius + spore=[], # only position and radius + clone=[[80, 100, 22, '0', '0'], [130, 80, 12, '1', '0'], + [130, 120, 16, '2', '1'], [300, 300, 3, '3', '1']], + ), + obs_settings=dict( + with_spatial=True, + with_speed=False, + with_all_vision=False, + ), +) diff --git a/gobigger/hyper/tests/test_actions.py b/gobigger/hyper/tests/test_actions.py new file mode 100644 index 0000000..b18f760 --- /dev/null +++ b/gobigger/hyper/tests/test_actions.py @@ -0,0 +1,86 @@ +import logging +import pytest + +from gobigger.hyper import StraightMergeHyperAction, QuarterMergeHyperAction, EighthMergeHyperAction +from gobigger.server import Server +from gobigger.render import EnvRender + +logging.basicConfig(level=logging.DEBUG) + +@pytest.mark.unittest +class TestHyperActions: + + def test_straight_merge(self): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=10 * 1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = EnvRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(420) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + sm_action = StraightMergeHyperAction(player_name1, player_name2) + + for _ in range(4): + obs = server.obs() + sm_action.update(obs[1][player_name1], obs[1][player_name2]) + actions = sm_action.get() + server.step(actions=actions) + + def test_quarter_merge(self): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=10 * 1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = EnvRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(420) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + qm_action = QuarterMergeHyperAction(player_name1, player_name2) + + for _ in range(4): + obs = server.obs() + qm_action.update(obs[1][player_name1], obs[1][player_name2]) + actions = qm_action.get() + server.step(actions=actions) + + def test_eighth_merge(self): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=10 * 1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = EnvRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(420) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + em_action = EighthMergeHyperAction(player_name1, player_name2) + + for _ in range(4): + obs = server.obs() + em_action.update(obs[1][player_name1], obs[1][player_name2]) + actions = em_action.get() + server.step(actions=actions) diff --git a/gobigger/hyper/tests/test_config.py b/gobigger/hyper/tests/test_config.py new file mode 100644 index 0000000..8fa42c1 --- /dev/null +++ b/gobigger/hyper/tests/test_config.py @@ -0,0 +1,26 @@ +import logging +import pytest + +from gobigger.hyper.configs.config_2f2s import server_default_config as c1 +from gobigger.hyper.configs.config_2f2s_v2 import server_default_config as c2 +from gobigger.hyper.configs.config_2f2s_v3 import server_default_config as c3 +from gobigger.server import Server + +logging.basicConfig(level=logging.DEBUG) + +@pytest.mark.unittest +class TestHyperConfig: + + def test_2f2s(self): + server = Server(c1) + + def test_2f2s_v2(self): + server = Server(c2) + + def test_2f2s_v3(self): + server = Server(c3) + + + + + diff --git a/gobigger/hyper/tests/test_demo.py b/gobigger/hyper/tests/test_demo.py new file mode 100644 index 0000000..52b0fdd --- /dev/null +++ b/gobigger/hyper/tests/test_demo.py @@ -0,0 +1,154 @@ +import pygame +import time +import logging + +from gobigger.hyper import StraightMergeHyperAction, QuarterMergeHyperAction, EighthMergeHyperAction +from gobigger.server import Server +from gobigger.render import RealtimeRender, RealtimePartialRender, EnvRender + + +def demo_straight_merge(): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=60*1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = RealtimeRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(420) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + sm_action = StraightMergeHyperAction(player_name1, player_name2) + fps_real = 0 + t1 = time.time() + clock = pygame.time.Clock() + fps_set = server.state_tick_per_second + for _ in range(100000): + obs = server.obs() + sm_action.update(obs[1][player_name1], obs[1][player_name2]) + action = sm_action.get() + if server.last_time < server.match_time: + for i in range(server.state_tick_per_action_tick): + if i == 0: + server.step_state_tick(actions=action) + else: + server.step_state_tick() + render.fill(server, direction=None, fps=fps_real, last_time=server.last_time, + player_num_per_team=server.player_num_per_team) + render.show() + if i % server.state_tick_per_second == 0: + t2 = time.time() + fps_real = server.state_tick_per_second/(t2-t1) + t1 = time.time() + clock.tick(fps_set) + else: + logging.debug('Game Over') + break + render.close() + + +def demo_quarter_merge(): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=60*1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = RealtimeRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(420) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + sm_action = QuarterMergeHyperAction(player_name1, player_name2) + fps_real = 0 + t1 = time.time() + clock = pygame.time.Clock() + fps_set = server.state_tick_per_second + for _ in range(100000): + obs = server.obs() + sm_action.update(obs[1][player_name1], obs[1][player_name2]) + action = sm_action.get() + print(action) + if server.last_time < server.match_time: + for i in range(server.state_tick_per_action_tick): + if i == 0: + server.step_state_tick(actions=action) + else: + server.step_state_tick() + render.fill(server, direction=None, fps=fps_real, last_time=server.last_time, + player_num_per_team=server.player_num_per_team) + render.show() + if i % server.state_tick_per_second == 0: + t2 = time.time() + fps_real = server.state_tick_per_second/(t2-t1) + t1 = time.time() + clock.tick(fps_set) + else: + logging.debug('Game Over') + break + render.close() + + +def demo_eighth_merge(): + server = Server(dict( + team_num=1, + player_num_per_team=2, + map_width=600, + map_height=600, + match_time=60*1, + state_tick_per_second=20, # frame + action_tick_per_second=5, # frame + )) + server.start() + render = RealtimeRender(server.map_width, server.map_height) + server.set_render(render) + server.player_manager.get_players()[0].get_balls()[0].set_size(820) + server.player_manager.get_players()[1].get_balls()[0].set_size(100) + player_name1 = server.player_manager.get_players()[0].name + player_name2 = server.player_manager.get_players()[1].name + sm_action = EighthMergeHyperAction(player_name1, player_name2) + fps_real = 0 + t1 = time.time() + clock = pygame.time.Clock() + fps_set = server.state_tick_per_second + for _ in range(100000): + obs = server.obs() + sm_action.update(obs[1][player_name1], obs[1][player_name2]) + action = sm_action.get() + print(action) + if server.last_time < server.match_time: + for i in range(server.state_tick_per_action_tick): + if i == 0: + server.step_state_tick(actions=action) + else: + server.step_state_tick() + render.fill(server, direction=None, fps=fps_real, last_time=server.last_time, + player_num_per_team=server.player_num_per_team) + render.show() + if i % server.state_tick_per_second == 0: + t2 = time.time() + fps_real = server.state_tick_per_second/(t2-t1) + t1 = time.time() + clock.tick(fps_set) + else: + logging.debug('Game Over') + break + render.close() + + +if __name__ == '__main__': + # demo_straight_merge() + # demo_quarter_merge() + demo_eighth_merge() + diff --git a/gobigger/managers/food_manager.py b/gobigger/managers/food_manager.py index 06c16d8..c8dacf0 100644 --- a/gobigger/managers/food_manager.py +++ b/gobigger/managers/food_manager.py @@ -13,10 +13,14 @@ class FoodManager(BaseManager): - def __init__(self, cfg, border): + def __init__(self, cfg, border, random_generator=None): super(FoodManager, self).__init__(cfg, border) self.food_refresh_time = self.cfg.refresh_time self.refresh_time_count = 0 + if random_generator is not None: + self._random = random_generator + else: + self._random = random.Random() def get_balls(self): return list(self.balls.values()) @@ -47,7 +51,7 @@ def spawn_ball(self, position=None, size=None): if position is None: position = self.border.sample() if size is None: - size = random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 + size = self._random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 name = uuid.uuid1() return FoodBall(name=name, position=position, border=self.border, size=size, **self.ball_settings) diff --git a/gobigger/managers/player_manager.py b/gobigger/managers/player_manager.py index 835df6f..23de879 100644 --- a/gobigger/managers/player_manager.py +++ b/gobigger/managers/player_manager.py @@ -1,3 +1,4 @@ +import random import math import logging import uuid @@ -13,7 +14,7 @@ class PlayerManager(BaseManager): - def __init__(self, cfg, border, team_num, player_num_per_team, spore_manager_settings): + def __init__(self, cfg, border, team_num, player_num_per_team, spore_manager_settings, random_generator=None): super(PlayerManager, self).__init__(cfg, border) self.players = {} self.team_num = team_num @@ -21,6 +22,10 @@ def __init__(self, cfg, border, team_num, player_num_per_team, spore_manager_set self.player_num = self.team_num * self.player_num_per_team self.spore_manager_settings = spore_manager_settings self.spore_settings = self.spore_manager_settings.ball_settings + if random_generator is not None: + self._random = random_generator + else: + self._random = random.Random() def init_balls(self, custom_init=None): if custom_init is None: @@ -54,18 +59,19 @@ def init_balls(self, custom_init=None): ball = CloneBall(team_name=team_name, name=uuid.uuid1(), position=position, border=self.border, size=radius**2, vel=Vector2(0,0), acc=Vector2(0,0), vel_last=Vector2(0,0), acc_last=Vector2(0,0), last_given_acc=Vector2(0,0), - stop_flag=True, owner=player_name, spore_settings=self.spore_settings) - ball.vel = Vector2(*ball_cfg[5:7]) - ball.acc = Vector2(*ball_cfg[7:9]) - ball.vel_last = Vector2(*ball_cfg[9:11]) - ball.acc_last = Vector2(*ball_cfg[11:13]) - ball.direction = Vector2(*ball_cfg[13:15]) - ball.last_given_acc = Vector2(*ball_cfg[15:17]) - ball.age = ball_cfg[17] - ball.cooling_last = ball_cfg[18] - ball.stop_flag = ball_cfg[19] - ball.stop_time = ball_cfg[20] - ball.acc_stop = Vector2(*ball_cfg[21:23]) + stop_flag=True, owner=player_name, spore_settings=self.spore_settings, **self.cfg.ball_settings) + if len(ball_cfg) > 5: + ball.vel = Vector2(*ball_cfg[5:7]) + ball.acc = Vector2(*ball_cfg[7:9]) + ball.vel_last = Vector2(*ball_cfg[9:11]) + ball.acc_last = Vector2(*ball_cfg[11:13]) + ball.direction = Vector2(*ball_cfg[13:15]) + ball.last_given_acc = Vector2(*ball_cfg[15:17]) + ball.age = ball_cfg[17] + ball.cooling_last = ball_cfg[18] + ball.stop_flag = ball_cfg[19] + ball.stop_time = ball_cfg[20] + ball.acc_stop = Vector2(*ball_cfg[21:23]) self.players[player_name].add_balls(ball) init_dict[team_name][player_name] = True for team_name, team in init_dict.items(): diff --git a/gobigger/managers/spore_manager.py b/gobigger/managers/spore_manager.py index f17316b..62acadb 100644 --- a/gobigger/managers/spore_manager.py +++ b/gobigger/managers/spore_manager.py @@ -13,8 +13,12 @@ class SporeManager(BaseManager): - def __init__(self, cfg, border): + def __init__(self, cfg, border, random_generator=None): super(SporeManager, self).__init__(cfg, border) + if random_generator is not None: + self._random = random_generator + else: + self._random = random.Random() def get_balls(self): return list(self.balls.values()) @@ -40,7 +44,7 @@ def spawn_ball(self, position=None, size=None): if position is None: position = self.border.sample() if size is None: - size = random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 + size = self._random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 name = uuid.uuid1() return SporeBall(name=name, position=position, border=self.border, direction=Vector2(1,0), vel_init=0) @@ -50,11 +54,12 @@ def init_balls(self, custom_init=None): if custom_init is not None: for ball_cfg in custom_init: ball = self.spawn_ball(position=Vector2(*ball_cfg[:2]), size=ball_cfg[2]**2) - ball.direction = Vector2(*ball_cfg[3:5]) - ball.vel = Vector2(*ball_cfg[5:7]) - ball.acc = Vector2(*ball_cfg[7:9]) - ball.move_time = ball_cfg[9] - ball.moving = ball_cfg[10] + if len(ball_cfg) > 3: + ball.direction = Vector2(*ball_cfg[3:5]) + ball.vel = Vector2(*ball_cfg[5:7]) + ball.acc = Vector2(*ball_cfg[7:9]) + ball.move_time = ball_cfg[9] + ball.moving = ball_cfg[10] self.balls[ball.name] = ball def step(self, duration): diff --git a/gobigger/managers/thorns_manager.py b/gobigger/managers/thorns_manager.py index 2579b2f..b5d9eaf 100644 --- a/gobigger/managers/thorns_manager.py +++ b/gobigger/managers/thorns_manager.py @@ -13,10 +13,14 @@ class ThornsManager(BaseManager): - def __init__(self, cfg, border): + def __init__(self, cfg, border, random_generator=None): super(ThornsManager, self).__init__(cfg, border) self.thorns_refresh_time = self.cfg.refresh_time self.refresh_time_count = 0 + if random_generator is not None: + self._random = random_generator + else: + self._random = random.Random() def get_balls(self): return list(self.balls.values()) @@ -47,7 +51,7 @@ def spawn_ball(self, position=None, size=None): if position is None: position = self.border.sample() if size is None: - size = random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 + size = self._random.uniform(self.ball_settings.radius_min, self.ball_settings.radius_max)**2 name = uuid.uuid1() return ThornsBall(name=name, position=position, border=self.border, size=size, **self.ball_settings) @@ -61,10 +65,11 @@ def init_balls(self, custom_init=None): else: for ball_cfg in custom_init: ball = self.spawn_ball(position=Vector2(*ball_cfg[:2]), size=ball_cfg[2]**2) - ball.vel = Vector2(*ball_cfg[3:5]) - ball.acc = Vector2(*ball_cfg[5:7]) - ball.move_time = ball_cfg[7] - ball.moving = ball_cfg[8] + if len(ball_cfg) > 3: + ball.vel = Vector2(*ball_cfg[3:5]) + ball.acc = Vector2(*ball_cfg[5:7]) + ball.move_time = ball_cfg[7] + ball.moving = ball_cfg[8] self.balls[ball.name] = ball def step(self, duration): diff --git a/gobigger/render/env_render.py b/gobigger/render/env_render.py index 8bd03be..6959ac4 100644 --- a/gobigger/render/env_render.py +++ b/gobigger/render/env_render.py @@ -3,7 +3,6 @@ import uuid from pygame.math import Vector2 import pygame -import random import numpy as np import cv2 import time diff --git a/gobigger/render/realtime_render.py b/gobigger/render/realtime_render.py index fe09122..d58bf65 100644 --- a/gobigger/render/realtime_render.py +++ b/gobigger/render/realtime_render.py @@ -3,7 +3,6 @@ import uuid from pygame.math import Vector2 import pygame -import random import cv2 import math @@ -30,8 +29,8 @@ def fill(self, server, direction=None, fps=0, last_time=0, player_num_per_team=3 thorns_balls=server.thorns_manager.get_balls(), spore_balls=server.spore_manager.get_balls(), players=server.player_manager.get_players(), - player_num_per_team=1) - # for debugƒ + player_num_per_team=player_num_per_team) + # for debug font= pygame.font.SysFont('Menlo', 15, True) team_name_size = {} diff --git a/gobigger/server/server.py b/gobigger/server/server.py index 5d5ddf5..0f28ac6 100644 --- a/gobigger/server/server.py +++ b/gobigger/server/server.py @@ -68,7 +68,7 @@ def default_config(): cfg = copy.deepcopy(server_default_config) return EasyDict(cfg) - def __init__(self, cfg=None): + def __init__(self, cfg=None, seed=None): self.cfg = Server.default_config() if isinstance(cfg, dict): cfg = EasyDict(cfg) @@ -97,18 +97,20 @@ def __init__(self, cfg=None): self.load_bin_frame_num = self.cfg.load_bin_frame_num self.obs_settings = self.cfg.obs_settings - self.border = Border(0, 0, self.map_width, self.map_height) + self.seed(seed) + self.border = Border(0, 0, self.map_width, self.map_height, self._random) self.last_time = 0 self.screens_all = [] self.screens_partial = {} self.actions_record = [] - self.food_manager = FoodManager(self.cfg.manager_settings.food_manager, border=self.border) - self.thorns_manager = ThornsManager(self.cfg.manager_settings.thorns_manager, border=self.border) - self.spore_manager = SporeManager(self.cfg.manager_settings.spore_manager, border=self.border) + self.food_manager = FoodManager(self.cfg.manager_settings.food_manager, border=self.border, random_generator=self._random) + self.thorns_manager = ThornsManager(self.cfg.manager_settings.thorns_manager, border=self.border, random_generator=self._random) + self.spore_manager = SporeManager(self.cfg.manager_settings.spore_manager, border=self.border, random_generator=self._random) self.player_manager = PlayerManager(self.cfg.manager_settings.player_manager, border=self.border, team_num=self.team_num, player_num_per_team=self.player_num_per_team, - spore_manager_settings=self.cfg.manager_settings.spore_manager) + spore_manager_settings=self.cfg.manager_settings.spore_manager, + random_generator=self._random) self.collision_detection_type = self.cfg.collision_detection_type self.collision_detection = create_collision_detection(self.collision_detection_type, border=self.border) @@ -260,7 +262,6 @@ def reset(self): self.screens_all = [] self.screens_partial = {} self.actions_record = [] - self.seed() self.load_record() self.food_manager.reset() self.thorns_manager.reset() @@ -421,4 +422,4 @@ def seed(self, seed=None): self._seed = random.randrange(sys.maxsize) else: self._seed = seed - random.seed(self._seed) + self._random = random.Random(self._seed) diff --git a/gobigger/utils/structures.py b/gobigger/utils/structures.py index 23f76a8..39e0368 100644 --- a/gobigger/utils/structures.py +++ b/gobigger/utils/structures.py @@ -46,13 +46,17 @@ class Border: Overview: used to specify a rectangular range ''' - def __init__(self, minx, miny, maxx, maxy): + def __init__(self, minx, miny, maxx, maxy, random_generator=None): self.minx = minx self.miny = miny self.maxx = maxx self.maxy = maxy self.width = self.maxx - self.minx self.height =self.maxy - self.miny + if random_generator is not None: + self._random = random_generator + else: + self._random = random.Random() def __repr__(self) -> str: return '[' + str(self.minx) + ',' + str(self.miny) + ',' + str(self.maxx) + ',' + str(self.maxy) + ']' @@ -75,8 +79,8 @@ def sample(self) -> Vector2: Returns: Vector2: the sampled position. ''' - x = random.uniform(self.minx, self.maxx) - y = random.uniform(self.miny, self.maxy) + x = self._random.uniform(self.minx, self.maxx) + y = self._random.uniform(self.miny, self.maxy) return Vector2(x, y) def get_joint(self, border) : @@ -86,7 +90,7 @@ def get_joint(self, border) : new_maxy = min(self.maxy, border.maxy) if new_minx > new_maxx or new_miny > new_maxy: return None - return Border(new_minx, new_maxx, new_miny, new_maxy) + return Border(new_minx, new_maxx, new_miny, new_maxy, self._random) class QuadNode: diff --git a/setup.py b/setup.py index 329f2bf..3bfa6b4 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='gobigger', - version='0.1.3', + version='0.1.4', description='Go-Bigger: Multi-Agent Decision Intelligence Environment', author='OpenDILab', license='Apache License, Version 2.0', @@ -23,6 +23,7 @@ 'gobigger.render', 'gobigger.envs', 'gobigger.bin', + 'gobigger.hyper', ], install_requires=[ 'easydict',