def _add_wall(self): if self.player_hide.walls_counter < self.player_hide.walls_max and not self.player_hide.wall_timer: wall_pos = copy.deepcopy(self.player_hide.pos) wall_size = (max(int(self.player_hide.width / 10), 2), max(int(self.player_hide.height / 2), 2)) # minimum 2x2 Wall vision_arc_range = np.sqrt((self.player_hide.vision_top.x - self.player_hide.pos.x) * (self.player_hide.vision_top.x - self.player_hide.pos.x) + ( self.player_hide.vision_top.y - self.player_hide.pos.y) * (self.player_hide.vision_top.y - self.player_hide.pos.y)) # vision arc range - 1.5 wall width, so the wall is always created inside PoV. wall_pos.x = wall_pos.x + vision_arc_range - \ (1.5 * wall_size[0]) wall_pos = Point.triangle_unit_circle_relative( self.player_hide.direction, self.player_hide.pos, wall_pos) wall = Wall(self.player_hide, wall_pos.x, wall_pos.y, wall_size, self.cfg['graphics_path_wall_owner']) wall._rotate(self.player_hide.direction, wall_pos) if self._can_create_wall(wall, self.agent_env['p_hide']['enemy']): self.player_hide.walls_counter += 1 self.walls_group.add(wall) self.player_hide.wall_timer = copy.deepcopy( self.player_hide.wall_timer_init) return True else: del wall return False
def _rotate(self, angle, position): """ Rotates the sprite by creating new Rectangle and updates its polygon points Parameters ---------- angle : float player direction in radians position : Point center of the wall Returns ------- None """ self.direction = angle # Copy and then rotate the original image. copied_image = self.image.copy() self.image = pygame.transform.rotozoom(copied_image, -angle * 180 / math.pi, 1) self.image.set_colorkey((0, 0, 0)) # Create a new rect with the center of the sprite. self.rect = self.image.get_rect() self.rect.center = (position.x, position.y) self.width = self.rect.width self.height = self.rect.height # Update the polygon points for collisions self.polygon_points = [ Point.triangle_unit_circle_relative(angle, self.pos, polygon_point) for polygon_point in self.polygon_points ]
def _determine_new_ray_points(self, wall_edges): """ Algorithm which calculates new possible ray points based on the current Agent Position and Agent Direction Parameters ---------- None Returns ------- ray_points : list of Point list of new, standard Ray Points """ ray_points = [] dir_t = self.direction - 2*math.pi if self.direction > math.pi else self.direction point_angle_0 = self.pos + Point((self.vision_radius, 0)) angles = np.linspace(dir_t - self.vision_rad / 2, dir_t + self.vision_rad / 2, num=11, endpoint=True) # counter-clockwise angles_min, angles_max = min(angles), max(angles) for p in [pnt for wall_edge in wall_edges for pnt in wall_edge]: delta = p - self.pos theta_radians = math.atan2(delta.y, delta.x) # 3rd quartet (2nd in math, 3rd in Y being from top to bottom), fixes 2nd; from [-PI; PI] to [-2PI; 0] if angles_min < -math.pi and theta_radians > 0: theta_radians = theta_radians - 2*math.pi # 2nd quartet (3rd in math, 2nd in Y being from top to bottom), fixes 3rd; from [-PI; PI] to [0; 2PI] elif angles_max > math.pi and theta_radians < 0: theta_radians = theta_radians + 2*math.pi if theta_radians not in angles and theta_radians > angles_min and theta_radians < angles_max: angles = np.append(angles, theta_radians) angles = np.sort(angles) for angle in angles: ray_point = Point.triangle_unit_circle_relative( angle, self.pos, point_angle_0) ray_points.append(ray_point) return ray_points
def update_vision(self, local_env): """ Updates Agent Vision Parameters ---------- local_env : dict contains Player Local Environment Returns ------- None """ new_point = Point.triangle_unit_circle( self.direction, side_size=self.vision_radius) self.vision_top = self.pos + new_point wall_edges = self.reduce_wall_edges(local_env['walls']) self.ray_points = self._determine_new_ray_points( wall_edges) # without center self.ray_points = self._find_intersections(wall_edges)[1:] # creates triangles self.ray_objects = [[self.pos, self.ray_points[i], self.ray_points[i + 1]] for i in range(len(self.ray_points) - 1) if self.ray_points[i] != self.ray_points[i + 1]] # adds Agent Rectangle to Agent Ray Objects speed_dist_w = self.speed + 1 + self.width / 2 speed_dist_h = self.speed + 1 + self.height / 2 self.ray_objects.append([ Point((self.pos.x - speed_dist_w, self.pos.y - speed_dist_h)), Point((self.pos.x + speed_dist_w, self.pos.y - speed_dist_h)), Point((self.pos.x + speed_dist_w, self.pos.y + speed_dist_h)), Point((self.pos.x - speed_dist_w, self.pos.y + speed_dist_h)), ])
def _perform_agent_action(self, agent, action, local_env): if action == 0: ''' agent.image_index = 0 agent.image = agent.images[agent.image_index] ''' return self._calc_action_reward(agent, action) elif action in [1, 2]: # (1 - 1.5) * 2 = -1, so for Forward it needs to be * (-1) x = math.cos(agent.direction) * agent.speed * \ (action - 1.5) * 2 * (-1) # (1 - 1.5) * 2 = -1, so for Forward it needs to be * (-1) y = math.sin(agent.direction) * agent.speed * \ (action - 1.5) * 2 * (-1) old_pos = copy.deepcopy(agent.pos) new_pos = agent.pos + Point((x, y)) self._move_agent(agent, new_pos) # outside game window if ( new_pos.x < 0 or new_pos.x > self.width or new_pos.y < 0 or new_pos.y > self.height ): self._move_agent(agent, old_pos) return self._calc_action_reward(agent, action, success=False) for wall in local_env['walls']: if Collision.aabb(new_pos, (agent.width, agent.height), wall.pos, (wall.width, wall.height)): if Collision.sat(agent.get_abs_vertices(), wall.get_abs_vertices()): self._move_agent(agent, old_pos) return self._calc_action_reward(agent, action, success=False) return self._calc_action_reward(agent, action) elif action in [3, 4]: # (3 - 3.5) * 2 = -1, so for Clockwise Rotate it needs to be * (-1) did_rotate = self._rotate_agent(agent, (action - 3.5) * 2 * (-1)) return self._calc_action_reward(agent, action, success=did_rotate) elif action == 5: if isinstance(agent, Seeker): did_remove = self._remove_wall() return self._calc_action_reward(agent, action, success=did_remove) else: did_add = self._add_wall() return self._calc_action_reward(agent, action, success=did_add) raise Exception( f"Unknown action, available action space: {self.action_space}")
def get_abs_vertices(self): """ Returns absolute coordinates of Vertices in Polygon Parameters ---------- None Returns ------- points : list of hidenseek.ext.supportive.Point self.pylogon_points mapped to the absolute coordinates system """ return [Point((polygon_point.x + self.rect.left, polygon_point.y + self.rect.top)) for polygon_point in self.polygon_points]
def _find_intersections(self, wall_edges): """ Algorithm which looks for new Ray Points, which are closer to the Agent Center than radius-distance Ray Points Parameters ---------- wall_edges : list of [Point, Point] list of Wall Edges in Agent Local Environment Returns ------- temp_ray_points : list of Point list of new Ray Points """ edges_bounding_boxes = [ { 'center': (edge[0] + edge[1]) / 2, 'size': (abs(edge[1].x - edge[0].x), abs(edge[1].y - edge[0].y)) } for edge in wall_edges ] temp_ray_points = [Point(self.rect.center)] for vertex in self.ray_points: # first must be the center point line_segment = [self.pos.round(4), vertex.round(4)] new_point = copy.deepcopy(vertex.round(4)) new_point_dist = self.pos.distance(new_point) bounding_box = { 'center': (line_segment[0] + line_segment[1]) / 2, 'size': (abs(line_segment[1].x - line_segment[0].x), abs(line_segment[1].y - line_segment[0].y)) } for edge, edge_bounding_box in zip(wall_edges, edges_bounding_boxes): if not Collision.aabb(bounding_box['center'], bounding_box['size'], edge_bounding_box['center'], edge_bounding_box['size']): continue p = Collision.line_intersection(line_segment, edge) if p and self.pos.distance(p) <= new_point_dist: new_point = p new_point_dist = self.pos.distance(p) temp_ray_points.append(new_point) return temp_ray_points
def __init__(self, owner, x, y, size, img_path, direction=0): """ Constructs all neccesary attributes for the Wall Object Parameters ---------- owner : None, hidenseek.objects.controllable.Hiding, hidenseek.objects.controllable.Seeker Wall owner, None for game environment x : float center of the rectangle in 'x' axis for absolute coordinate system (game screen) y : float center of the rectangle in 'y' axis for absolute coordinate system (game screen) size : tuple Wall size, at least 2x2 """ super().__init__() self.owner = owner self.width = size[0] self.height = size[1] self.pos = Point((x, y)) self.pos_init = Point((x, y)) image = pygame.Surface((self.width, self.height)) image.fill((0, 0, 0, 0)) image.set_colorkey((0, 0, 0)) self.image = image self.filling = [ pygame.image.load(os.path.join(img_path, file_)) for file_ in os.listdir(img_path) ] self.rect = self.image.get_rect() self.rect.center = (self.pos.x, self.pos.y) filling_width = self.filling[0].get_width() filling_height = self.filling[0].get_height() img_full_size_w = self.width / filling_width img_rounded_size_w = math.ceil(img_full_size_w) img_full_size_h = self.height / filling_height img_rounded_size_h = math.ceil(img_full_size_h) blit_list = [(self.filling[0], (filling_width * i, j * filling_height)) for i in range(0, img_rounded_size_w) for j in range(0, img_rounded_size_h)] image.blits(blit_list) self.polygon_points = [ Point((self.rect.left, self.rect.top)), Point((self.rect.right, self.rect.top)), Point((self.rect.right, self.rect.bottom)), Point((self.rect.left, self.rect.bottom)) ] self.direction = direction
def __init__(self, cfg, size, pos_ratio, SCREEN_WIDTH, SCREEN_HEIGHT): """ Constructs all neccesary attributes for the Player Object Parameters ---------- cfg : configparser Object Agent Config Object size : tuple Agent size pos_ratio : tuple used to calculate initial position of the Player in absolute coordinate system (game screen); if value < 1 then it's ratio in percentage, otherwise it's coord SCREEN_WIDTH : int width of the game window SCREEN_HEIGHT : int height of the game window """ super().__init__() self.width = size[0] self.height = size[1] tmp_pos = list(pos_ratio) if tmp_pos[0] < 1: tmp_pos[0] *= SCREEN_WIDTH if tmp_pos[1] < 1: tmp_pos[1] *= SCREEN_HEIGHT self.pos = Point(tmp_pos) self.pos_init = Point(tmp_pos) self.SCREEN_WIDTH = SCREEN_WIDTH self.SCREEN_HEIGHT = SCREEN_HEIGHT self.cfg = cfg self.speed = cfg['speed_ratio'] self.speed_rotate = cfg['speed_rotate_ratio'] self.wall_timer_init = cfg['wall_action_timeout'] self.wall_timer = cfg['wall_action_timeout'] self.vision_radius = self.width * 2 self.vision_top = None self.ray_objects = None self.vision_rad = math.pi self.ray_points = [] self.direction = 0 # radians from which vision_rad is added/substracted """ OCTAGON > 0.15 on every side because we need to cover only 0.7 of space, to be able to freely rotate without making bigger rectangle > 2*x / sqrt(2) + x = 0.7 > x ~= 0.29 > x / sqrt(2) ~= 0.205 x/sqrt(2) x x/sqrt(2) ........---------........ ....../ \...... ...../ \..... ..../ \.... .../ \... ..| |.. ..| |.. ..| |.. ..| |.. ..| |.. ...\ /... ....\ /.... .....\ /..... ......\ /...... ........---------........ """ self.polygon_points = [ Point((self.width * .355, self.height * .15)), Point((self.width * .645, self.height * .15)), Point((self.width * .85, self.height * .355)), Point((self.width * .85, self.height * .645)), Point((self.width * .645, self.height * .85)), Point((self.width * .355, self.height * .85)), Point((self.width * .15, self.height * .645)), Point((self.width * .15, self.height * .355)), ] self.sprites = [pygame.image.load(os.path.join( cfg['graphics_path'], file_)) for file_ in os.listdir(cfg['graphics_path'])] surface = pygame.Surface((self.width, self.height)) surface.set_colorkey((0, 0, 0)) self.image_index = 0 self.image = surface self.rect = self.image.get_rect() self.rect.center = (self.pos.x, self.pos.y)
class Player(pygame.sprite.Sprite): """ Parent Player Class for Hide'n'Seek Game, inherits from pygame.sprite.Sprite. Shouldn't be used because it doesn't have implementation of few methods Attributes ---------- width : int width of the Player Rectangle height : int height of the Player Rectangle SCREEN_WIDTH : int width of the game window SCREEN_HEIGHT : int height of the game window pos : hidenseek.ext.supportive.Point object position on the game display speed : int speed ratio for Player movement (in ticks) speed_rotate : float speed ratio for Player rotate wall_timer_init : int init cooldown (in frames) for any wall-specific action wall_timer : int cooldown (in frames)for any wall-specific action vision_radius : float Player POV radius vision_rad : float Player POV angle vision_top : Point Player Top POV Point ray_points : list of Point Player Ray POV in Points representation ray_objects : list of Objects (3-el list of Point) Player Ray POV in Triangles representation direction : float POV angle in radians (Z = 2 * PI) image_index : int determines which image should be drawn images : list of pygame.Surface objects with sprite/images from which the proper one will be drawn image : pygame.Surface object with sprite/image, chosen by 'image_index' rect : pygame.Rect object Rectangle, to be drawn polygon_points : list of tuples Agent vertices, used for collision check in SAT actions : list of dict contains all possible Player actions Methods ------- _rotate(turn, local_env): rotates the object, accordingly to the value, along its axis get_abs_vertices(): returns absolute vertices coordinates (in game screen coordinates system) _move_action(new_pos): algorithm which moves the Player object to given poisition update_vision(local_env): updates Agent POV update(local_env): Not implemented in Parent Class """ def __init__(self, cfg, size, pos_ratio, SCREEN_WIDTH, SCREEN_HEIGHT): """ Constructs all neccesary attributes for the Player Object Parameters ---------- cfg : configparser Object Agent Config Object size : tuple Agent size pos_ratio : tuple used to calculate initial position of the Player in absolute coordinate system (game screen); if value < 1 then it's ratio in percentage, otherwise it's coord SCREEN_WIDTH : int width of the game window SCREEN_HEIGHT : int height of the game window """ super().__init__() self.width = size[0] self.height = size[1] tmp_pos = list(pos_ratio) if tmp_pos[0] < 1: tmp_pos[0] *= SCREEN_WIDTH if tmp_pos[1] < 1: tmp_pos[1] *= SCREEN_HEIGHT self.pos = Point(tmp_pos) self.pos_init = Point(tmp_pos) self.SCREEN_WIDTH = SCREEN_WIDTH self.SCREEN_HEIGHT = SCREEN_HEIGHT self.cfg = cfg self.speed = cfg['speed_ratio'] self.speed_rotate = cfg['speed_rotate_ratio'] self.wall_timer_init = cfg['wall_action_timeout'] self.wall_timer = cfg['wall_action_timeout'] self.vision_radius = self.width * 2 self.vision_top = None self.ray_objects = None self.vision_rad = math.pi self.ray_points = [] self.direction = 0 # radians from which vision_rad is added/substracted """ OCTAGON > 0.15 on every side because we need to cover only 0.7 of space, to be able to freely rotate without making bigger rectangle > 2*x / sqrt(2) + x = 0.7 > x ~= 0.29 > x / sqrt(2) ~= 0.205 x/sqrt(2) x x/sqrt(2) ........---------........ ....../ \...... ...../ \..... ..../ \.... .../ \... ..| |.. ..| |.. ..| |.. ..| |.. ..| |.. ...\ /... ....\ /.... .....\ /..... ......\ /...... ........---------........ """ self.polygon_points = [ Point((self.width * .355, self.height * .15)), Point((self.width * .645, self.height * .15)), Point((self.width * .85, self.height * .355)), Point((self.width * .85, self.height * .645)), Point((self.width * .645, self.height * .85)), Point((self.width * .355, self.height * .85)), Point((self.width * .15, self.height * .645)), Point((self.width * .15, self.height * .355)), ] self.sprites = [pygame.image.load(os.path.join( cfg['graphics_path'], file_)) for file_ in os.listdir(cfg['graphics_path'])] surface = pygame.Surface((self.width, self.height)) surface.set_colorkey((0, 0, 0)) self.image_index = 0 self.image = surface self.rect = self.image.get_rect() self.rect.center = (self.pos.x, self.pos.y) def act(self, obs, reward, game_end, action_space): """ Decides on the action Parameters ---------- obs : PLACEHOLDER What agent sees. reward : float Reward for previous action. game_end : boolean contains Player Local Environment action_space : spaces.Discrete actions possible to perform. Returns ------- None """ action = action_space.sample() return action def get_abs_vertices(self): """ Returns absolute coordinates of Vertices in Polygon Parameters ---------- None Returns ------- points : list of hidenseek.ext.supportive.Point self.pylogon_points mapped to the absolute coordinates system """ return [Point((polygon_point.x + self.rect.left, polygon_point.y + self.rect.top)) for polygon_point in self.polygon_points] def _determine_new_ray_points(self, wall_edges): """ Algorithm which calculates new possible ray points based on the current Agent Position and Agent Direction Parameters ---------- None Returns ------- ray_points : list of Point list of new, standard Ray Points """ ray_points = [] dir_t = self.direction - 2*math.pi if self.direction > math.pi else self.direction point_angle_0 = self.pos + Point((self.vision_radius, 0)) angles = np.linspace(dir_t - self.vision_rad / 2, dir_t + self.vision_rad / 2, num=11, endpoint=True) # counter-clockwise angles_min, angles_max = min(angles), max(angles) for p in [pnt for wall_edge in wall_edges for pnt in wall_edge]: delta = p - self.pos theta_radians = math.atan2(delta.y, delta.x) # 3rd quartet (2nd in math, 3rd in Y being from top to bottom), fixes 2nd; from [-PI; PI] to [-2PI; 0] if angles_min < -math.pi and theta_radians > 0: theta_radians = theta_radians - 2*math.pi # 2nd quartet (3rd in math, 2nd in Y being from top to bottom), fixes 3rd; from [-PI; PI] to [0; 2PI] elif angles_max > math.pi and theta_radians < 0: theta_radians = theta_radians + 2*math.pi if theta_radians not in angles and theta_radians > angles_min and theta_radians < angles_max: angles = np.append(angles, theta_radians) angles = np.sort(angles) for angle in angles: ray_point = Point.triangle_unit_circle_relative( angle, self.pos, point_angle_0) ray_points.append(ray_point) return ray_points def reduce_wall_edges(self, walls): """ Algorithm which reduces wall edges from 4 per Wall to only 2 (closest ones) Parameters ---------- walls : list of Wall list of Walls in Agent Local Environment Returns ------- proper_walls_lines : list of [Point, Point] list of closest wall edges """ walls_lines = [[[wall.get_abs_vertices()[i % 4], wall.get_abs_vertices()[ (i + 1) % 4]] for i in range(4)] for wall in walls] # get only closer parallel wall edge, reduces computation by half proper_walls_lines = [] for wall_lines in walls_lines: if self.pos.distance(wall_lines[0][0] + (wall_lines[0][1] - wall_lines[0][0]) / 2) < self.pos.distance(wall_lines[2][0] + (wall_lines[2][1] - wall_lines[2][0]) / 2): proper_walls_lines.append(wall_lines[0]) else: proper_walls_lines.append(wall_lines[2]) if self.pos.distance(wall_lines[1][0] + (wall_lines[1][1] - wall_lines[1][0]) / 2) < self.pos.distance(wall_lines[3][0] + (wall_lines[3][1] - wall_lines[3][0]) / 2): proper_walls_lines.append(wall_lines[1]) else: proper_walls_lines.append(wall_lines[3]) return proper_walls_lines def _find_intersections(self, wall_edges): """ Algorithm which looks for new Ray Points, which are closer to the Agent Center than radius-distance Ray Points Parameters ---------- wall_edges : list of [Point, Point] list of Wall Edges in Agent Local Environment Returns ------- temp_ray_points : list of Point list of new Ray Points """ edges_bounding_boxes = [ { 'center': (edge[0] + edge[1]) / 2, 'size': (abs(edge[1].x - edge[0].x), abs(edge[1].y - edge[0].y)) } for edge in wall_edges ] temp_ray_points = [Point(self.rect.center)] for vertex in self.ray_points: # first must be the center point line_segment = [self.pos.round(4), vertex.round(4)] new_point = copy.deepcopy(vertex.round(4)) new_point_dist = self.pos.distance(new_point) bounding_box = { 'center': (line_segment[0] + line_segment[1]) / 2, 'size': (abs(line_segment[1].x - line_segment[0].x), abs(line_segment[1].y - line_segment[0].y)) } for edge, edge_bounding_box in zip(wall_edges, edges_bounding_boxes): if not Collision.aabb(bounding_box['center'], bounding_box['size'], edge_bounding_box['center'], edge_bounding_box['size']): continue p = Collision.line_intersection(line_segment, edge) if p and self.pos.distance(p) <= new_point_dist: new_point = p new_point_dist = self.pos.distance(p) temp_ray_points.append(new_point) return temp_ray_points def update_vision(self, local_env): """ Updates Agent Vision Parameters ---------- local_env : dict contains Player Local Environment Returns ------- None """ new_point = Point.triangle_unit_circle( self.direction, side_size=self.vision_radius) self.vision_top = self.pos + new_point wall_edges = self.reduce_wall_edges(local_env['walls']) self.ray_points = self._determine_new_ray_points( wall_edges) # without center self.ray_points = self._find_intersections(wall_edges)[1:] # creates triangles self.ray_objects = [[self.pos, self.ray_points[i], self.ray_points[i + 1]] for i in range(len(self.ray_points) - 1) if self.ray_points[i] != self.ray_points[i + 1]] # adds Agent Rectangle to Agent Ray Objects speed_dist_w = self.speed + 1 + self.width / 2 speed_dist_h = self.speed + 1 + self.height / 2 self.ray_objects.append([ Point((self.pos.x - speed_dist_w, self.pos.y - speed_dist_h)), Point((self.pos.x + speed_dist_w, self.pos.y - speed_dist_h)), Point((self.pos.x + speed_dist_w, self.pos.y + speed_dist_h)), Point((self.pos.x - speed_dist_w, self.pos.y + speed_dist_h)), ]) def reset(self): self.pos = copy.deepcopy(self.pos_init) self.wall_timer = copy.deepcopy(self.wall_timer_init) self.vision_top = None self.ray_objects = None self.direction = 0 surface = pygame.Surface((self.width, self.height)) surface.set_colorkey((0, 0, 0)) self.image_index = 0 self.image = surface self.rect = self.image.get_rect() self.rect.center = (self.pos.x, self.pos.y)