def get_boundary_walls(x_axis: int, y_axis: int): """Get a set of the boundary walls.""" a = Vec2d(0, 0) b = Vec2d(x_axis, 0) c = Vec2d(x_axis, y_axis) d = Vec2d(0, y_axis) return {Line2d(a, b), Line2d(b, c), Line2d(c, d), Line2d(d, a)}
def combine_walls(wall_list: list): """ Combine every two wall-segments (Line2d) together that can be represented by only one line segment. This will increase the performance later on, since the intersection methods must loop over less wall-segments. :param wall_list: List<Line2d> :return: List<Line2d> """ i = 0 while i < len(wall_list) - 1: concat = False # Check if wall at i can be concatenated with wall after i for w in wall_list[i + 1:]: # Check if in same horizontal line if wall_list[i].x.y == wall_list[i].y.y == w.x.y == w.y.y: # Check if at least one point collides if wall_list[i].x.x == w.x.x: wall_list[i] = Line2d(Vec2d(wall_list[i].y.x, w.x.y), Vec2d(w.y.x, w.x.y)) concat = True elif wall_list[i].y.x == w.x.x: wall_list[i] = Line2d(Vec2d(wall_list[i].x.x, w.x.y), Vec2d(w.y.x, w.x.y)) concat = True elif wall_list[i].y.x == w.y.x: wall_list[i] = Line2d(Vec2d(wall_list[i].x.x, w.x.y), Vec2d(w.x.x, w.x.y)) concat = True elif wall_list[i].x.x == w.y.x: wall_list[i] = Line2d(Vec2d(wall_list[i].y.x, w.x.y), Vec2d(w.x.x, w.x.y)) concat = True # Check if in same vertical line elif wall_list[i].x.x == wall_list[i].y.x == w.x.x == w.y.x: # Check if at least one point collides if wall_list[i].x.y == w.x.y: wall_list[i] = Line2d(Vec2d(w.x.x, wall_list[i].y.y), Vec2d(w.x.x, w.y.y)) concat = True elif wall_list[i].y.y == w.x.y: wall_list[i] = Line2d(Vec2d(w.x.x, wall_list[i].x.y), Vec2d(w.x.x, w.y.y)) concat = True elif wall_list[i].y.y == w.y.y: wall_list[i] = Line2d(Vec2d(w.x.x, wall_list[i].x.y), Vec2d(w.x.x, w.x.y)) concat = True elif wall_list[i].x.y == w.y.y: wall_list[i] = Line2d(Vec2d(w.x.x, wall_list[i].y.y), Vec2d(w.x.x, w.x.y)) concat = True # If w concatenated with i'th wall in wall_list, then remove w from list and break for-loop if concat: wall_list.remove(w) break # Current wall cannot be extended, go to next wall if not concat: i += 1
def create_custom_game(cfg: Config, overwrite=False): """ Dummy to create a custom-defined game. """ # Initial parameters game_id = 0 # Create empty Game instance game = Game(config=cfg, game_id=game_id, overwrite=overwrite) # Put the target on a fixed position game.target = Vec2d(0.5, cfg.game.y_axis - 0.5) # Set game path p = dict() # TODO! for x in range(0, cfg.game.x_axis * 10 + 1): # TODO! for y in range(0, cfg.game.x_axis * 10 + 1): # TODO! p[(x / 10, y / 10)] = Line2d(game.target, Vec2d(x / 10, y / 10)).get_length() game.path = p # Create random player game.player = MarXBot(game=game) game.set_player_init_angle(a=np.pi / 2) game.set_player_init_pos(p=Vec2d(cfg.game.x_axis - 0.5, 0.5)) # Check if implemented correctly game.close() game.reset() game.get_blueprint() game.get_observation() game.step(0, 0) # Save the final game game.save()
def measure(self, close_walls: set = None): """ Get the distance to the closest wall. If all the walls are 'far enough', as determined by self.max_dist, then the maximum sensor-distance is returned. :param close_walls: Walls which fall within ray_distance from the agent, speeds up readings :return: Float expressing the distance to the closest wall, if there is any """ # Start and end point of ray normalized_offset = Vec2d(cos(self.game.player.angle + self.angle), sin(self.game.player.angle + self.angle)) self.start_pos = self.game.player.pos + normalized_offset * self.pos_offset self.end_pos = self.game.player.pos + normalized_offset * ( self.pos_offset + self.max_dist) sensor_line = Line2d(x=self.game.player.pos, y=self.end_pos) # Check if there is a wall intersecting with the sensor and return the closest distance to a wall self.value = self.max_dist for wall in close_walls if close_walls else self.game.walls: inter, pos = line_line_intersection(sensor_line, wall) if inter: new_dist = (pos - self.start_pos).get_length() if self.value > new_dist: self.end_pos = pos self.value = new_dist if self.game.noise: self.value += random.gauss(0, self.game.noise_proximity) self.value = max(0, min(self.value, self.max_dist))
def test_simple_length(self): """> Test for simple line-segments.""" # Folder must be root to load in make_net properly if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..') # Create simple lines a = Vec2d(1, 1) b = Vec2d(1, 2) c = Vec2d(2, 2) line1 = Line2d(a, b) line2 = Line2d(b, c) line3 = Line2d(a, c) # Test the length self.assertTrue(1 - EPSILON <= line1.get_length() <= 1 + EPSILON) self.assertTrue(1 - EPSILON <= line2.get_length() <= 1 + EPSILON) self.assertTrue(sqrt(2) - EPSILON <= line3.get_length() <= sqrt(2) + EPSILON)
def test_other_angles(self): """> Check if drone cannot force itself through a wall.""" # Folder must be root to load in make_net properly if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..') # Create the lines zero = Vec2d(0, 0) a = Vec2d(1, 1) line1 = Line2d(zero, a) # Tests self.assertTrue(pi / 4 - EPSILON <= line1.get_orientation() % (2 * pi) <= pi / 4 + EPSILON)
def test_quadrant_angles(self): """> Check if drone cannot force itself through a wall.""" # Folder must be root to load in make_net properly if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..') # Create the lines zero = Vec2d(0, 0) a = Vec2d(1, 0) b = Vec2d(0, 1) c = Vec2d(-1, 0) d = Vec2d(0, -1) line1 = Line2d(zero, a) line2 = Line2d(zero, b) line3 = Line2d(zero, c) line4 = Line2d(zero, d) # Tests self.assertTrue(0 - EPSILON <= line1.get_orientation() % (2 * pi) <= 0 + EPSILON) self.assertTrue(pi / 2 - EPSILON <= line2.get_orientation() % (2 * pi) <= pi / 2 + EPSILON) self.assertTrue(pi - EPSILON <= line3.get_orientation() % (2 * pi) <= pi + EPSILON) self.assertTrue(3 * pi / 2 - EPSILON <= line4.get_orientation() % (2 * pi) <= 3 * pi / 2 + EPSILON)
def get_wall_coordinates(self): """ :return: Wall coordinates of final maze (excluding boundaries) in original axis-format. """ wall_list = [] # Horizontal segments for x in range(1, self.x_width - 1, 2): for y in range(2, self.y_width, 2): if self.maze[y, x] == -1: wall_list.append( Line2d(Vec2d((x - 1) // 2, y // 2), Vec2d((x + 1) // 2, y // 2))) # Vertical segments for x in range(2, self.x_width, 2): for y in range(1, self.y_width - 1, 2): if self.maze[y, x] == -1: wall_list.append( Line2d(Vec2d(x // 2, (y - 1) // 2), Vec2d(x // 2, (y + 1) // 2))) combine_walls(wall_list) return wall_list
def load(self): """ Load in a game, specified by its current id. :return: True: game successfully loaded | False: otherwise """ try: game = load_pickle(f'{self.save_path}{self}') self.player = MarXBot(game=self) # Create a dummy-player to set values on self.set_player_init_angle(game[D_ANGLE]) self.set_player_init_pos(Vec2d(game[D_POS][0], game[D_POS][1])) self.path = {p[0]: p[1] for p in game[D_PATH]} self.target = Vec2d(game[D_TARGET][0], game[D_TARGET][1]) self.walls = {Line2d(Vec2d(w[0][0], w[0][1]), Vec2d(w[1][0], w[1][1])) for w in game[D_WALLS]} if not self.silent: print(f"Existing game loaded with id: {self.id}") return True except FileNotFoundError: return False
def test_negative_component(self): """> Test for line-segments with negative components.""" # Folder must be root to load in make_net properly if os.getcwd().split('\\')[-1] == 'tests': os.chdir('..') # Create simple lines a = Vec2d(1, 1) b = Vec2d(1, -1) c = Vec2d(-1, -1) d = Vec2d(-1, 1) line1 = Line2d(a, b) line2 = Line2d(b, c) line3 = Line2d(c, d) line4 = Line2d(d, a) diag1 = Line2d(a, c) diag2 = Line2d(b, d) # Test the length self.assertTrue(2 - EPSILON <= line1.get_length() <= 2 + EPSILON) self.assertTrue(2 - EPSILON <= line2.get_length() <= 2 + EPSILON) self.assertTrue(2 - EPSILON <= line3.get_length() <= 2 + EPSILON) self.assertTrue(2 - EPSILON <= line4.get_length() <= 2 + EPSILON) self.assertTrue(sqrt(8) - EPSILON <= diag1.get_length() <= sqrt(8.) + EPSILON) self.assertTrue(sqrt(8) - EPSILON <= diag2.get_length() <= sqrt(8.) + EPSILON)