def __init__(self, position): self.can_climb = False self.is_climbing = False self.hook_type = HookType.rope self.max_hooks = 1 self.image = pygame.image.load('assets/gfx/Player.png') self.sheet_size = Vec2D(self.image.get_width(), self.image.get_height()) Object2D.__init__( self, Vec2D(self.image.get_width() / 4, self.image.get_height() / 2), position) self.rect = (0, 0, self.width, self.height) self.force = Vec2D() self.direction = Vec2D() self.frame_left = 0 self.frame_right = 0 self.last_direction = 0 self.last_shooting = 0 self.can_shoot = True self.ladder_span = Vec2D() self.invulnerability_timer = 0 self.invulnerable = False self.invulnerability_rect = pygame.Surface((self.width, self.height), pygame.SRCALPHA, 32) self.invulnerability_rect.fill((255, 0, 255, 192))
def __init__(self, position, bonus_type): self.bonus_type = bonus_type Object2D.__init__(self, Vec2D(BONUS_SIZE, BONUS_SIZE), position) self.force = Vec2D() self.to_kill = False self.fall = True self.timer = BONUS_DURATION
def split_ball(self, ball): self.play_sound('BallPop.wav') if ball.radius > MIN_BALL_RADIUS: self.balls.append(Ball(ball.radius//2, Vec2D(ball.x - ball.radius//2, ball.y), Vec2D(-abs(ball.force.x), -GRAVITY))) self.balls.append(Ball(ball.radius//2, Vec2D(ball.x + ball.radius//2, ball.y), Vec2D(abs(ball.force.x), -GRAVITY))) self.spawn_bonus(ball.position) self.score += ball.radius
def calculate_force(self, force): """Function takes as a parameter the Vec2D object returned from the collision function and changes the x/y direction if the force isn't 0 """ if force.x: self.force = Vec2D(-self.force.x, self.force.y) if force.y: if self.y < self.max_height: self.force = Vec2D(self.force.x, 0) else: if self.falling: self.force = Vec2D( self.force.x, -(abs( (self.y - self.max_height) * 2 * GRAVITY)**.5)) else: self.force = Vec2D(self.force.x, -self.force.y)
def __init__(self, height, position, hook_type): self.hook_type = hook_type self.to_kill = False Object2D.__init__(self, Vec2D(6, height), position) self.expand = True self.timer = HOOK_DURATION
def __init__(self, radius, position, force): Object2D.__init__(self, Vec2D(radius * 2, radius * 2), position) self.radius = radius self.position = position self.force = force self.falling = True if force.y < 0: self.falling = False self.max_height = SCREEN_HEIGHT - 150 - radius * 4
def update(self, time_passed): self.force += (0, GRAVITY * time_passed) self.position += self.force * time_passed if self.x < self.radius or self.x > SCREEN_WIDTH - self.radius: self.force = Vec2D(-self.force.x, self.force.y) if self.x < self.radius: self.position = Vec2D(2 * self.radius - self.x, self.y) else: self.position = Vec2D( 2 * (SCREEN_WIDTH - self.radius) - self.x, self.y) if self.y > SCREEN_HEIGHT - self.radius: self.position = Vec2D(self.x, 2 * (SCREEN_HEIGHT - self.radius) - self.y) self.force = Vec2D( self.force.x, -(((self.y - self.max_height) * 2 * GRAVITY)**.5)) if self.force.y > 0: self.falling = True else: self.falling = False
def update(self, time_passed): if not self.fall: self.timer -= time_passed if self.timer < 0: self.to_kill = True else: self.force += (0, GRAVITY * time_passed) self.position += (0, self.force.y * time_passed) if self.y > SCREEN_HEIGHT - self.height: self.position = Vec2D(self.x, SCREEN_HEIGHT - self.height) self.fall = False
def player_shoot(self): if not self.player.is_climbing and \ self.player.max_hooks > len(self.hooks) and self.player.can_shoot: self.play_sound('HookShoot.wav') self.hooks.append(Hook(20, Vec2D(self.player.x + self.player.width/2, self.player.y + self.player.height - 20), self.player.hook_type)) self.player.last_shooting = HOOK_RELOAD_TIME self.player.can_shoot = False
def add_option(self, caption, function, arg=None): text = self.font.render(caption, True, self.color) text_selected = self.font.render('-> ' + caption + ' <-', True, self.selected_color) if arg: self.labels[len(self.labels.items())] = (text, text_selected, function, arg) else: self.labels[len(self.labels.items())] = (text, text_selected, function) if len(self.labels.items()) == 1: self.selected_option = 0 self.positions = [] items = len(self.labels.items()) max_height = 0 if self.title: max_height = self.title_text.get_rect().height items += 2 for key, value in self.labels.items(): text_height = value[0].get_rect().height if text_height > max_height: max_height = text_height current_y_pos = (SCREEN_HEIGHT - (items * max_height + (items - 1) * MENU_LABEL_MARGIN)) // 2 if self.y_position: current_y_pos = self.y_position if self.title: x_pos = (SCREEN_WIDTH - self.title_text.get_rect().width) // 2 y_pos = current_y_pos + (max_height - self.title_text.get_rect().height) // 2 self.title_position = Vec2D(x_pos, y_pos) current_y_pos += 2 * (max_height + MENU_LABEL_MARGIN) for key, value in self.labels.items(): x_pos = (SCREEN_WIDTH - value[0].get_rect().width) // 2 y_pos = current_y_pos + (max_height - value[0].get_rect().height) // 2 position = Vec2D(x_pos, y_pos) current_y_pos += max_height + MENU_LABEL_MARGIN self.positions.append(position)
def __init__(self, title=None, y_position=0): self.labels = OrderedDict() self.font = pygame.font.SysFont(None, MENU_LABEL_FONT_SIZE) self.selected_color = (255, 0, 0) self.color = (255, 255, 255) self.selected_option = None self.title = title self.y_position = y_position if self.title: self.title_text = self.font.render(title, True, self.color) self.title_position = Vec2D() self.music_path = 'assets/music/Menu.wav' self.positions = []
def process_event(self, event): key = pygame.key.get_pressed() direction = Vec2D() if key[pygame.K_LEFT]: direction -= (1, 0) if key[pygame.K_RIGHT]: direction += (1, 0) if key[pygame.K_UP]: direction -= (0, 1) if key[pygame.K_DOWN]: direction += (0, 1) self.player.set_direction(direction) if key[pygame.K_SPACE]: self.player_shoot()
def update(self, time_passed): if not self.expand: self.timer -= time_passed if self.timer < 0: self.to_kill = True if self.y > 0 and self.expand: increment = HOOK_SPEED * time_passed self.size += (0, increment) self.position -= (0, increment) if self.y < 0: if self.hook_type == HookType.rope: self.to_kill = True elif self.hook_type == HookType.chain: self.size += (0, self.y) self.position = Vec2D(self.x, 0) self.expand = False
def get_ladder_image(self, string, width, height): image_library = self.ladder_cache.get(string) size_string = str(width) + 'x' + str(height) image = None if image_library is None: image_library = {} self.ladder_cache[string] = image_library image = image_library.get(size_string) if image is None: tile_image = pygame.image.load(string) tile = Vec2D(tile_image.get_size()) image = pygame.Surface([width, height], flags=pygame.SRCALPHA) for y in range(0, height + 1, tile.height): image.blit(tile_image, (0, y)) self.ladder_cache[string][size_string] = image return image
def get_obstacle_image(self, string, width, height): image_library = self.obstacle_cache.get(self.obstacle_image) size_string = str(width) + 'x' + str(height) image = None if image_library is None: image_library = {} self.obstacle_cache[string] = image_library image = image_library.get(size_string) if image is None: tile_image = pygame.image.load(self.obstacle_image) tile = Vec2D(tile_image.get_size()) image = pygame.Surface([width, height]) for x in range(0, width + 1, tile.width): for y in range(0, height + 1, tile.height): image.blit(tile_image, (x, y)) self.obstacle_cache[string][size_string] = image return image
def box_to_box(box1, box2, player=False, bonus=False, hook=False): """Separating Axis Theorem used for AABB collision and different fixations based on parameters. If one of the flags is set to True, the first object should be movable and the second static. """ if (abs(box1.x + box1.width / 2 - (box2.x + box2.width / 2)) >= (box1.width / 2 + box2.width / 2)): return False if (abs(box1.y + box1.height / 2 - (box2.y + box2.height / 2)) >= (box1.height / 2 + box2.height / 2)): return False if player: if int(box1.y + box1.height) in range(int(box2.y + 10)): box1.position = Vec2D(box1.x, box2.y - box1.height) box1.force = Vec2D(box1.force.x, 0) else: if box1.x + box1.width - box2.x < box2.x + box2.width - box1.x: box1.position = Vec2D(box2.x - box1.width, box1.y) else: box1.position = Vec2D(box2.x + box2.width, box1.y) if bonus: box1.position = Vec2D(box1.x, box2.y - box1.height) box1.force = Vec2D(box1.force.x, 0) box1.fall = False if hook and box1.y > box2.y: if box1.hook_type == HookType.chain: fixation = box2.y + box2.height - box1.y box1.position += (0, fixation) box1.size -= (0, fixation) box1.expand = False elif box1.hook_type == HookType.rope: box1.to_kill = True return True
def check_collisions(self): for ball in self.balls: for obstacle in self.obstacles: result = ball_to_box(ball, obstacle, True) if result: ball.calculate_force(result) if self.player.invulnerable: for ball in self.balls: result = ball_to_box(ball, self.player, True) if result: ball.calculate_force(result) self.player.can_climb = False for ladder in self.ladders: if player_to_ladder(self.player, ladder): self.player.can_climb = True self.player.ladder_span = Vec2D(ladder.y, ladder.y + ladder.height) if not self.player.is_climbing: for obstacle in self.obstacles: box_to_box(self.player, obstacle, player=True) for hook in self.hooks: for obstacle in self.obstacles: if box_to_box(hook, obstacle, hook=True): break for hook_index in range(len(self.hooks) - 1, -1, -1): hook = self.hooks[hook_index] if hook.to_kill: del self.hooks[hook_index] else: for ball_index in range(len(self.balls) - 1, -1, -1): ball = self.balls[ball_index] if ball_to_box(ball, hook): self.split_ball(ball) del self.balls[ball_index] del self.hooks[hook_index] break for bonus_index in range(len(self.bonuses) - 1, -1, -1): bonus = self.bonuses[bonus_index] if bonus.to_kill: del self.bonuses[bonus_index] elif box_to_box(bonus, self.player): self.activate_bonus(bonus) del self.bonuses[bonus_index] for bonus_index in range(len(self.bonuses) - 1, -1, -1): bonus = self.bonuses[bonus_index] for obstacle in self.obstacles: box_to_box(bonus, obstacle, bonus=True) for ball in self.balls: if ball_to_box(ball, self.player): self.lives -= 1 if self.lives: self.load_level(self.current_level) break else: self.game_over = True break
def load_game(self, file_name): file_path = 'assets/save/' + str(file_name) + '.save' if not os.path.isfile(file_path): return None file = open(file_path, 'r') lines = file.readlines() lines = list(map(str.rstrip, lines)) match = re.match(r'level (\d+)', lines[0]) level_id = list(map(int, match.groups()))[0] self.load_level(level_id, scene_only=True, keep_score=False) ball_regex = (r'ball radius (\d+) pos (\d+), ' + '(\d+) force (-?\d+), (-?\d+)') player_regex = (r'player pos (\d+), (\d+) hooktype (\d+) maxhooks ' + '(\d+) force (-?\d+), (-?\d+)') bonus_regex = (r'bonus pos (\d+), (\d+) bonustype (\d+) duration ' + '(\d+.\d+|\d+) force (-?\d+), (-?\d+) fall (\d+) ' + 'tokill (\d+)') hook_regex = (r'hook pos (\d+), (\d+) hooktype (\d+) height (\d+) ' + 'duration (\d+.\d+|\d+) expand (\d+) tokill (\d+)') world_regex = r'world ballstimer (\d+.\d+|\d+) score (\d+) lives (\d+)' for line in lines[1:]: if re.match(ball_regex, line): match = re.match(ball_regex, line) radius, x, y, force_x, force_y = list(map(int, match.groups())) self.balls.append(Ball(radius, Vec2D(x, y), Vec2D(force_x, force_y))) elif re.match(player_regex, line): match = re.match(player_regex, line) pos_x = int(match.group(1)) pos_y = int(match.group(2)) hook_type = int(match.group(3)) max_hooks = int(match.group(4)) force_x = int(match.group(5)) force_y = int(match.group(6)) self.player = Player(Vec2D(pos_x, pos_y)) self.player.hook_type = hook_type self.player.max_hooks = max_hooks self.player.force = Vec2D(force_x, force_y) elif re.match(bonus_regex, line): match = re.match(bonus_regex, line) pos_x = int(match.group(1)) pos_y = int(match.group(2)) bonus_type = int(match.group(3)) duration = float(match.group(4)) force_x = int(match.group(5)) force_y = int(match.group(6)) fall = bool(int(match.group(7))) to_kill = bool(int(match.group(8))) bonus = Bonus(Vec2D(pos_x, pos_y), bonus_type) bonus.timer = duration bonus.force = Vec2D(force_x, force_y) bonus.fall = fall bonus.to_kill = to_kill self.bonuses.append(bonus) elif re.match(hook_regex, line): match = re.match(hook_regex, line) pos_x = int(match.group(1)) pos_y = int(match.group(2)) hook_type = int(match.group(3)) height = int(match.group(4)) duration = float(match.group(5)) expand = bool(int(match.group(6))) to_kill = bool(int(match.group(7))) hook = Hook(height, Vec2D(pos_x, pos_y), hook_type) hook.timer = duration hook.expand = expand hook.to_kill = to_kill self.hooks.append(hook) elif re.match(world_regex, line): match = re.match(world_regex, line) balls_timer, score, lives = list(map(float, match.groups())) self.balls_timer = balls_timer if balls_timer > 0: self.balls_paused = True else: self.balls_paused = False self.score = int(score) self.lives = int(lives)
def load_level(self, level_index, scene_only=False, keep_score=True): self.active = True file_path = 'assets/levels/' + str(level_index) + '.pang' if not os.path.isfile(file_path): return None self.balls = [] self.bonuses = [] self.hooks = [] self.obstacles = [] self.ladders = [] self.sound_library = {} self.text_cache = {} self.balls_paused = False self.balls_timer = 0 if not keep_score: self.score = 0 self.current_level = level_index self.level_pause = True self.pause_remaining = PAUSE_ON_LEVEL_LOAD file = open(file_path, 'r') lines = file.readlines() lines = list(map(str.rstrip, lines)) match = re.match(r'background (\S+)', lines[0]) background = list(map(str, match.groups()))[0] self.background = pygame.image.load('assets/gfx/' + background) tile_name = '' ball_regex = (r'ball radius (\d+) pos (\d+), ' + '(\d+) force (-?\d+), (-?\d+)') obstacle_regex = r'obstacle size (\d+), (\d+) pos (\d+), (\d+)' for line in lines[1:]: if not scene_only and re.match(ball_regex, line): match = re.match(ball_regex, line) radius, x, y, force_x, force_y = list(map(int, match.groups())) self.balls.append(Ball(radius, Vec2D(x, y), Vec2D(force_x, force_y))) elif re.match(obstacle_regex, line): match = re.match(obstacle_regex, line) width, height, x, y = list(map(int, match.groups())) self.obstacles.append(Obstacle(Vec2D(width, height), Vec2D(x, y))) elif not scene_only and re.match(r'player pos (\d+), (\d+)', line): match = re.match(r'player pos (\d+), (\d+)', line) point_x, point_y = list(map(int, match.groups())) point = Vec2D(point_x, point_y) self.player = Player(point) elif re.match(r'ladder height (\d+) pos (\d+), (\d+)', line): match = re.match(r'ladder height (\d+) pos (\d+), (\d+)', line) height, point_x, point_y = list(map(int, match.groups())) point = Vec2D(point_x, point_y) self.ladders.append(Ladder(height, point)) elif re.match(r'music (\S+)', line): match = re.match(r'music (\S+)', line) music = list(map(str, match.groups()))[0] self.music_path = 'assets/music/' + music pygame.mixer.music.load(self.music_path) pygame.mixer.music.play(-1) elif re.match(r'bricktile (\S+)', line): match = re.match(r'bricktile (\S+)', line) tile_name = list(map(str, match.groups()))[0] self.gui_drawer.obstacle_image = 'assets/gfx/' + tile_name return True
def setUp(self): self.box = Obstacle(Vec2D(200, 200), Vec2D(200, 200)) self.test_boxes = [] self.test_boxes.append(Obstacle(Vec2D(400, 100), Vec2D(100, 250))) self.test_boxes.append(Obstacle(Vec2D(100, 400), Vec2D(250, 100))) self.test_boxes.append(Obstacle(Vec2D(100, 200), Vec2D(250, 100))) self.test_boxes.append(Obstacle(Vec2D(100, 200), Vec2D(250, 300))) self.test_boxes.append(Obstacle(Vec2D(200, 100), Vec2D(100, 250))) self.test_boxes.append(Obstacle(Vec2D(200, 100), Vec2D(300, 250))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(300, 100))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(100, 100))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(100, 300))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(300, 300))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(300, -50))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(500, 500))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(500, 300))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(400, 200))) self.test_boxes.append(Obstacle(Vec2D(200, 200), Vec2D(200, 0))) self.player_results = [] self.test_boxes.append(Obstacle(Vec2D(80, 100), Vec2D(200, 105))) self.player_results.append(Vec2D(200, 100)) self.test_boxes.append(Obstacle(Vec2D(80, 100), Vec2D(350, 115))) self.player_results.append(Vec2D(400, 115)) self.test_boxes.append(Obstacle(Vec2D(80, 100), Vec2D(250, 115))) self.player_results.append(Vec2D(120, 115))
def update(self, time_passed): if self.invulnerable: self.invulnerability_timer -= time_passed if self.invulnerability_timer <= 0: self.invulnerability_timer = 0 self.invulnerable = False if not self.is_climbing: if self.y < SCREEN_HEIGHT - self.height: self.force += (0, GRAVITY * time_passed) self.position += (0, self.force.y * time_passed) if self.direction.x: self.position += ((self.direction.x * time_passed * PLAYER_SPEED), 0) if self.direction.x > 0: self.frame_right += time_passed self.frame_left = 0 else: self.frame_left += time_passed self.frame_right = 0 else: self.frame_right = 0 self.frame_left = 0 if self.x < 0: self.position = Vec2D(0, self.y) elif self.x > SCREEN_WIDTH - self.width: self.position = Vec2D(SCREEN_WIDTH - self.width, self.y) else: self.force = Vec2D(self.force.x, 0) if self.can_climb: if self.direction.y: self.position += (0, (self.direction.y * time_passed * PLAYER_SPEED)) self.is_climbing = True if self.y + self.height <= self.ladder_span[0]: self.position = Vec2D(self.x, self.ladder_span[0] - self.height) self.can_climb = True self.is_climbing = False elif self.y + self.height >= self.ladder_span[1]: self.position = Vec2D(self.x, self.ladder_span[1] - self.height) self.can_climb = True self.is_climbing = False self.frame_right = 0 self.frame_left = 0 else: self.is_climbing = False if self.y > SCREEN_HEIGHT - self.height: self.position = Vec2D(self.x, SCREEN_HEIGHT - self.height) self.is_climbing = False #calculate animation to display update_speed = 1. / PLAYER_ANIM_SPEED * GAME_SPEED if self.frame_right: self.frame_right %= 4 * update_speed frame = int(self.frame_right / update_speed) % 4 self.rect = (frame * self.width, 0, self.width, self.height) elif self.frame_left: self.frame_left %= 4 * update_speed frame = int(self.frame_left / update_speed) % 4 self.rect = (frame * self.width, self.height, self.width, self.height) else: if self.last_direction == -1: self.rect = (0, self.height, self.width, self.height) else: self.rect = (0, 0, self.width, self.height) if not self.can_shoot: self.last_shooting -= time_passed if self.last_shooting <= 0: self.can_shoot = True
def setUp(self): self.balls = [] self.results = [] self.box = Obstacle(Vec2D(200, 200), Vec2D(200, 200)) self.balls.append(Ball(150, Vec2D(200, 100), Vec2D(50, 50))) self.results.append((Vec2D(200, 0), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(200, 100), Vec2D(-50, 50))) self.results.append((Vec2D(200, 0), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(400, 100), Vec2D(50, 50))) self.results.append((Vec2D(400, 0), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(400, 100), Vec2D(-50, 50))) self.results.append((Vec2D(400, 0), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(450, 200 - 50 * (3**.5)), Vec2D(50, 50))) self.results.append((Vec2D(450, 3.76), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(450, 200 - 50 * (3**.5)), Vec2D(-50, 50))) self.results.append((Vec2D(450, 3.76), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(400 + 50 * (3**.5), 150), Vec2D(-50, 50))) self.results.append((Vec2D(596.24, 150), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(400 + 50 * (3**.5), 150), Vec2D(-50, -50))) self.results.append((Vec2D(596.24, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 200), Vec2D(-50, 50))) self.results.append((Vec2D(600, 200), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 200), Vec2D(-50, -50))) self.results.append((Vec2D(600, 200), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 400), Vec2D(-50, 50))) self.results.append((Vec2D(600, 400), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 400), Vec2D(-50, -50))) self.results.append((Vec2D(600, 400), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(400 + 50 * (3**.5), 450), Vec2D(-50, 50))) self.results.append((Vec2D(596.24, 450), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(400 + 50 * (3**.5), 450), Vec2D(-50, -50))) self.results.append((Vec2D(596.24, 450), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(450, 400 + 50 * (3**.5)), Vec2D(50, -50))) self.results.append((Vec2D(450, 596.24), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(450, 400 + 50 * (3**.5)), Vec2D(-50, -50))) self.results.append((Vec2D(450, 596.24), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(400, 500), Vec2D(50, -50))) self.results.append((Vec2D(400, 600), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(400, 500), Vec2D(-50, -50))) self.results.append((Vec2D(400, 600), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(200, 500), Vec2D(50, -50))) self.results.append((Vec2D(200, 600), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(200, 500), Vec2D(-50, -50))) self.results.append((Vec2D(200, 600), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(150, 400 + 50 * (3**.5)), Vec2D(50, -50))) self.results.append((Vec2D(150, 596.24), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(150, 400 + 50 * (3**.5)), Vec2D(-50, -50))) self.results.append((Vec2D(150, 596.24), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(200 - 50 * (3**.5), 450), Vec2D(50, -50))) self.results.append((Vec2D(3.76, 450), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(200 - 50 * (3**.5), 450), Vec2D(50, 50))) self.results.append((Vec2D(3.76, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 400), Vec2D(50, -50))) self.results.append((Vec2D(0, 400), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 400), Vec2D(50, 50))) self.results.append((Vec2D(0, 400), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 200), Vec2D(50, -50))) self.results.append((Vec2D(0, 200), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 200), Vec2D(50, 50))) self.results.append((Vec2D(0, 200), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(200 - 50 * (3**.5), 150), Vec2D(50, -50))) self.results.append((Vec2D(3.76, 150), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(200 - 50 * (3**.5), 150), Vec2D(50, 50))) self.results.append((Vec2D(3.76, 150), Vec2D(-1, 0))) self.balls.append( Ball(150, Vec2D(150, 200 - 50 * (3**.5)), Vec2D(50, 50))) self.results.append((Vec2D(150, 3.76), Vec2D(0, -1))) self.balls.append( Ball(150, Vec2D(150, 200 - 50 * (3**.5)), Vec2D(-50, 50))) self.results.append((Vec2D(150, 3.76), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(300, 100), Vec2D(50, 50))) self.results.append((Vec2D(300, 0), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(300, 100), Vec2D(-50, 50))) self.results.append((Vec2D(300, 0), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 100), Vec2D(50, 50))) self.results.append((Vec2D(450, 17.16), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 100), Vec2D(-50, 50))) self.results.append((Vec2D(450, 17.16), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 150), Vec2D(50, 50))) self.results.append((Vec2D(450, -32.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 150), Vec2D(-50, 50))) self.results.append((Vec2D(337.87, 37.87), Vec2D(-1, -1))) self.balls.append(Ball(150, Vec2D(450, 150), Vec2D(-50, -50))) self.results.append((Vec2D(632.84, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 150), Vec2D(-50, 50))) self.results.append((Vec2D(582.84, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 150), Vec2D(-50, -50))) self.results.append((Vec2D(582.84, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 300), Vec2D(-50, 50))) self.results.append((Vec2D(600, 300), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 300), Vec2D(-50, -50))) self.results.append((Vec2D(600, 300), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 450), Vec2D(-50, 50))) self.results.append((Vec2D(582.84, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(500, 450), Vec2D(-50, -50))) self.results.append((Vec2D(582.84, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(450, 450), Vec2D(-50, 50))) self.results.append((Vec2D(632.84, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(450, 450), Vec2D(-50, -50))) self.results.append((Vec2D(562.13, 562.13), Vec2D(-1, -1))) self.balls.append(Ball(150, Vec2D(450, 450), Vec2D(50, -50))) self.results.append((Vec2D(450, 632.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 500), Vec2D(-50, -50))) self.results.append((Vec2D(450, 582.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(450, 500), Vec2D(50, -50))) self.results.append((Vec2D(450, 582.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(300, 500), Vec2D(-50, -50))) self.results.append((Vec2D(300, 600), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(300, 500), Vec2D(50, -50))) self.results.append((Vec2D(300, 600), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 500), Vec2D(-50, -50))) self.results.append((Vec2D(150, 582.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 500), Vec2D(50, -50))) self.results.append((Vec2D(150, 582.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 450), Vec2D(-50, -50))) self.results.append((Vec2D(150, 632.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 450), Vec2D(50, -50))) self.results.append((Vec2D(262.13, 562.13), Vec2D(-1, -1))) self.balls.append(Ball(150, Vec2D(150, 450), Vec2D(50, 50))) self.results.append((Vec2D(-32.84, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 450), Vec2D(50, -50))) self.results.append((Vec2D(17.16, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 450), Vec2D(50, 50))) self.results.append((Vec2D(17.16, 450), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 300), Vec2D(50, -50))) self.results.append((Vec2D(0, 300), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 300), Vec2D(50, 50))) self.results.append((Vec2D(0, 300), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 150), Vec2D(50, -50))) self.results.append((Vec2D(17.16, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(100, 150), Vec2D(50, 50))) self.results.append((Vec2D(17.16, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(150, 150), Vec2D(50, -50))) self.results.append((Vec2D(-32.84, 150), Vec2D(-1, 0))) self.balls.append(Ball(150, Vec2D(150, 150), Vec2D(50, 50))) self.results.append((Vec2D(37.87, 37.87), Vec2D(-1, -1))) self.balls.append(Ball(150, Vec2D(150, 150), Vec2D(-50, 50))) self.results.append((Vec2D(150, -32.84), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 100), Vec2D(50, 50))) self.results.append((Vec2D(150, 17.16), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(150, 100), Vec2D(-50, 50))) self.results.append((Vec2D(150, 17.16), Vec2D(0, -1))) self.balls.append(Ball(150, Vec2D(0, 0), Vec2D(50, 0))) self.results.append((Vec2D(0, 0), None))
def test_angle(self): self.assertAlmostEqual(0, calc_angle(Vec2D(1, 0))) self.assertAlmostEqual(30, calc_angle(Vec2D((3**.5) / 2, .5))) self.assertAlmostEqual(30, calc_angle(Vec2D((3**.5) / 2, -.5))) self.assertAlmostEqual(45, calc_angle(Vec2D(1, 1))) self.assertAlmostEqual(45, calc_angle(Vec2D(1, -1))) self.assertAlmostEqual(60, calc_angle(Vec2D(.5, (3**.5) / 2))) self.assertAlmostEqual(60, calc_angle(Vec2D(.5, -(3**.5) / 2))) self.assertAlmostEqual(90, calc_angle(Vec2D(0, 1))) self.assertAlmostEqual(90, calc_angle(Vec2D(0, -1))) self.assertAlmostEqual(120, calc_angle(Vec2D(-.5, (3**.5) / 2))) self.assertAlmostEqual(120, calc_angle(Vec2D(-.5, -(3**.5) / 2))) self.assertAlmostEqual(135, calc_angle(Vec2D(-1, 1))) self.assertAlmostEqual(135, calc_angle(Vec2D(-1, -1))) self.assertAlmostEqual(150, calc_angle(Vec2D(-(3**.5) / 2, .5))) self.assertAlmostEqual(150, calc_angle(Vec2D(-(3**.5) / 2, -.5))) self.assertAlmostEqual(180, calc_angle(Vec2D(-1, 0)))
def __init__(self, height, position): Object2D.__init__(self, Vec2D(LADDER_WIDTH, height), position)
def ball_to_box(ball, box, solid=False): """Ball to box collision detection algorithm using voronoi regions. Calculates which region the ball is in based on this map: 1|2|3 4|0|5 6|7|8 Fixates the ball's position so that the objects no longer collide if solid is set to True and returns a Vec2D object representing the directions which the ball needs to change. """ radius = ball.radius region = -1 box_x_edge = 0 box_y_edge = 0 force_x = 0 force_y = 0 ball_x_fixate = 0 ball_y_fixate = 0 if ball.x > box.x + box.width: # 3 or 5 or 8 if ball.y > box.y + box.height: # 8 box_x_edge = box.x + box.width box_y_edge = box.y + box.height if (ball.x - box_x_edge)**2 + (ball.y - box_y_edge)**2 < radius**2: region = 8 elif ball.y < box.y: # 3 box_x_edge = box.x + box.width box_y_edge = box.y if (ball.x - box_x_edge)**2 + (ball.y - box_y_edge)**2 < radius**2: region = 3 else: # 5 if box.x + box.width >= ball.x - radius: region = 5 ball_x_fixate = 2 * (box.x + box.width - (ball.x - radius)) force_x = -1 elif ball.x < box.x: # 1 or 4 or 6 if ball.y > box.y + box.height: # 6 box_x_edge = box.x box_y_edge = box.y + box.height if (ball.x - box_x_edge)**2 + (ball.y - box_y_edge)**2 < radius**2: region = 6 elif ball.y < box.y: # 1 box_x_edge = box.x box_y_edge = box.y if (ball.x - box_x_edge)**2 + (ball.y - box_y_edge)**2 < radius**2: region = 1 else: # 4 if box.x <= ball.x + radius: region = 4 ball_x_fixate = 2 * (box.x - (ball.x + radius)) force_x = -1 else: # 2 or 7 or 0 if ball.y >= box.y + box.height: # 7 if box.y + box.height >= ball.y - radius: region = 7 ball_y_fixate = 2 * (box.y + box.height - (ball.y - radius)) force_y = -1 elif ball.y <= box.y: # 2 if box.y <= ball.y + radius: region = 2 ball_y_fixate = 2 * (box.y - (ball.y + radius)) force_y = -1 else: # 0 region = 0 if ball.force.y >= 0: # this is actually the same as 2 ball_y_fixate = 2 * (box.y - (ball.y + radius)) force_y = -1 else: # this is actually the same as 7 ball_y_fixate = 2 * (box.y + box.height - (ball.y - radius)) force_y = -1 if solid: if region in [1, 3, 6, 8]: delta_x = ball.x - box_x_edge delta_y = ball.y - box_y_edge if region in [1, 6]: if ball.force.x > 0: force_x = -1 else: if ball.force.x < 0: force_x = -1 if region in [1, 3]: if ball.force.y > 0: force_y = -1 else: if ball.force.y < 0: force_y = -1 angle = calc_angle(Vec2D(delta_x, delta_y)) if angle > 90.5: angle %= 90 if region in [1, 6]: if angle <= 31: force_x = 0 elif angle >= 59: force_y = 0 else: if angle <= 31: force_y = 0 elif angle >= 59: force_x = 0 if force_x and force_y: force_length = ((delta_x)**2 + (delta_y)**2)**.5 lengthen_y = radius / force_length - 1 lengthen_x = lengthen_y * ball.force.x / ball.force.y ball_x_fixate = 2 * delta_x * lengthen_x ball_y_fixate = 2 * delta_y * lengthen_y elif force_x: x_pos = (radius**2 - (box_y_edge - ball.y)**2)**.5 if ball.x > box_x_edge: x_pos = ball.x - x_pos ball_x_fixate = 2 * (box_x_edge - x_pos) else: x_pos += ball.x ball_x_fixate = 2 * (box_x_edge - x_pos) elif force_y: y_pos = (radius**2 - (box_x_edge - ball.x)**2)**.5 if ball.y > box_y_edge: y_pos = ball.y - y_pos ball_y_fixate = 2 * (box_y_edge - y_pos) else: y_pos += ball.y ball_y_fixate = 2 * (box_y_edge - y_pos) ball.position += (ball_x_fixate, ball_y_fixate) elif region in [0, 2, 4, 5, 7]: ball.position += (ball_x_fixate, ball_y_fixate) if region != -1: return Vec2D(force_x, force_y)