class Portal(Block): P_TYPE_1 = 1 P_TYPE_2 = 2 TYPE_1_COLOR = (0, 255, 255) TYPE_2_COLOR = (255, 128, 0) def __init__(self, screen, x, y, direction, maze, p_type=P_TYPE_1): self.screen = screen self.maze = maze self.direction = direction self.p_type = p_type if p_type == Portal.P_TYPE_1: self.images = ImageRect('blue-portal-bg.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (64, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32), (64, 32, 32, 32), (0, 64, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=250) image, _ = self.images.get_image() else: self.images = ImageRect('orange-portal-bg.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (64, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32), (64, 32, 32, 32), (0, 64, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=250) image, _ = self.images.get_image() super().__init__(x, y, maze.block_size, maze.block_size, image) def get_nearest_col(self): return (self.rect.x - (self.screen.get_width() // 5)) // self.maze.block_size def get_nearest_row(self): return (self.rect.y - (self.screen.get_height() // 12)) // self.maze.block_size def update(self): self.image = self.images.next_image() def blit(self): self.screen.blit(self.image, self.rect)
class Animate(Sprite): def __init__(self, screen, sprite_sheet, sheet_offsets, pos=(0, 0), resize=None, detail=None, frame_delay=None, flip=False): super().__init__() self.screen = screen if not resize: resize = (self.screen.get_height() // 10, self.screen.get_height() // 10) self.image_manager = ImageRect(sprite_sheet, sheet=True, pos_offsets=sheet_offsets, resize=resize, animation_delay=frame_delay) if flip: self.image_manager.flip() self.image, self.rect = self.image_manager.get_image() if detail: self.detail_piece = ImageRect(detail, sheet=True, pos_offsets=sheet_offsets, resize=resize).all_images()[0] if flip: self.image_manager.flip() self.image.blit(self.detail_piece, (0, 0)) else: self.detail_piece = None self.rect.centerx, self.rect.centery = pos def update(self): self.image = self.image_manager.next_image() if self.detail_piece: self.image.blit(self.detail_piece, (0, 0)) def blit(self): self.screen.blit(self.image, self.rect)
class Pacman(Sprite): audio = 0 yellow = (255, 255, 0) def __init__(self, screen, maze): super().__init__() self.screen = screen self.maze = maze self.dead = False self.direction = None self.event_map = { pygame.KEYDOWN: self.move, pygame.KEYUP: self.reset_movement } self.input_map = { pygame.K_UP: self.move_up, pygame.K_LEFT: self.move_left, pygame.K_DOWN: self.move_down, pygame.K_RIGHT: self.move_right, pygame.K_q: self.blue_portal, pygame.K_w: self.orange_portal } self.sound_manager = Sounds(sound_files=[ 'pacman-pellet-eat.wav', 'pacman-fruit-eat.wav', 'pacman-killed.wav', 'pacman-portal.wav' ], keys=['eat', 'fruit', 'dead', 'portal'], channel=Pacman.audio) self.death_frames = ImageRect('pacman-death.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32), (0, 64, 32, 32), (32, 64, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=150, repeat=False) self.pacmanx = ImageRect('pacman-horiz.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32), (0, 64, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), reversible=True) self.pacmany = ImageRect('pacman-vert.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32), (0, 64, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), reversible=True) self.spawn_set = self.maze.player_spawn[1] self.flip_mo = {'use_horiz': True, 'h_flip': False, 'v_flip': False} self.movement = False self.image, self.rect = self.pacmanx.get_image() self.portals = PortalController(screen, self, maze) def blue_portal(self): self.portals.fire_blue() def orange_portal(self): self.portals.fire_orange() def respawn(self): self.dead = False self.image, _ = self.pacmanx.get_image() self.death_frames.image_index = 0 def eat(self): score = 0 fruit_count = 0 ppellet = None collision = pygame.sprite.spritecollideany(self, self.maze.pellets) if collision: collision.kill() score += 10 self.sound_manager.play('eat') collision = pygame.sprite.spritecollideany(self, self.maze.fruits) if collision: collision.kill() score += (100 + (fruit_count * 200)) fruit_count += 1 self.sound_manager.play('fruit') collision = pygame.sprite.spritecollideany(self, self.maze.power_pellets) if collision: collision.kill() score += 50 ppellet = True self.sound_manager.play('eat') return score, fruit_count, ppellet def reset(self): self.rect.centerx, self.rect.centery = self.spawn_set def change_direction(self, event): if event.key in (pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT): self.moving = False def get_nearest_col(self): return (self.rect.x - (self.screen.get_width() // 5)) // self.maze.block_size def get_nearest_row(self): return (self.rect.y - (self.screen.get_height() // 12)) // self.maze.block_size def set_death(self): self.sound_manager.play('dead') self.dead = True self.image, _ = self.death_frames.get_image() def reset_portals(self): self.portals.clear_portals() def move(self, event): if event.key in self.input_map: self.input_map[event.key]() def move_up(self): if self.direction != 'u': self.direction = 'u' if self.flip_mo['v_flip']: self.pacmany.flip(False, True) self.flip_mo['v_flip'] = False self.flip_mo['use_horiz'] = False self.moving = True def move_left(self): if self.direction != 'l': self.direction = 'l' if not self.flip_mo['h_flip']: self.pacmanx.flip() self.flip_mo['h_flip'] = True self.flip_mo['use_horiz'] = True self.moving = True def move_down(self): if self.direction != 'd': self.direction = 'd' if not self.flip_mo['v_flip']: self.pacmany.flip(x_bool=False, y_bool=True) self.flip_mo['v_flip'] = True self.flip_mo['use_horiz'] = False self.movement = True def move_right(self): if self.direction != 'r': self.direction = 'r' if self.flip_mo['h_flip']: self.pacmanx.flip() self.flip_mo['h_flip'] = False self.flip_mo['use_horiz'] = True self.movement = True def reset_movement(self, event): if event.key in (pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT): self.movement = False def blit(self): self.portals.blit() self.screen.blit(self.image, self.rect)
class Ghost(Sprite): audio = 1 def __init__(self, screen, maze, target, spawn, sounds, ghost='ghost-red.png'): super().__init__() self.screen = screen self.maze = maze self.maze_block = Group() self.target = target self.direction = None self.last = None self.state = { 'normal': False, 'blue': False, 'return': False, 'rubber_band': False } self.blink = False self.last_blink = time.get_ticks() self.score_display = None self.normal_frames = ImageRect(ghost, sheet=True, pos_offsets=[(0, 0, 32, 32), (0, 32, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=250) self.image, self.rect = self.normal_frames.get_image() self.start_position = spawn[1] self.reset() self.tile = spawn[0] self.speed = maze.block_size / 10 self.blink_i = 250 self.eat = None self.return_path = None self.return_offset = 1000 self.grid = maze.map_lines self.sounds = sounds self.blue_i = 5000 self.blue_start = None self.eyes = ImageRect('ghost-eyes.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (32, 0, 32, 32), (0, 32, 32, 32), (32, 32, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), keys=['r', 'u', 'd', 'l']) self.font = sysfont.SysFont(None, 22) self.blue_frames = ImageRect('ghost-ppellet.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (0, 32, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=150) self.blue_white = ImageRect('ghost-ppellet-warn.png', sheet=True, pos_offsets=[(0, 0, 32, 32), (0, 32, 32, 32)], resize=(self.maze.block_size, self.maze.block_size), animation_delay=150) self.neutral_eye, _ = self.eyes.get_image(key='r') @staticmethod def arr_path(maze_map, start, target): path = [] tried = set() done = False curr_tile = start while not done: if curr_tile == target: done = True else: options = [(curr_tile[0] + 1, curr_tile[1]), (curr_tile[0] - 1, curr_tile[1]), (curr_tile[0], curr_tile[1] + 1), (curr_tile[0], curr_tile[1] - 1)] test = (abs(target[0] - start[0]), abs(target[1] - start[0])) prefer = test.index(max(test[0], test[1])) if prefer == 0: options.sort(key=lambda x: x[0], reverse=True) else: options.sort(key=lambda x: x[1], reverse=True) backtrack = True for opt in options: try: if maze_map[opt[0]][opt[1]] not in ( 'x', ) and opt not in tried: backtrack = False path.append(opt) tried.add(opt) curr_tile = opt break except IndexError: continue if backtrack: curr_tile = path.pop() return path def blit(self): self.screen.blit(self.image, self.rect) def ghost_eaten(self): self.state['return'] = True self.state['blue'] = False self.tile = (self.get_nearest_row(), self.get_nearest_col()) self.return_path = Ghost.arr_path(self.grid, self.tile, self.tile) self.direction = self.get_direction() self.image = self.font.render('200', True, (255, 255, 255)) self.eat = time.get_ticks() def begin_blue(self): if not self.state['return']: self.state['blue'] = True def stop_blue(self, resume_audio=True): self.state['blue'] = False self.state['return'] = False self.image, _ = self.normal_frames.get_image() def get_nearest_col(self): return (self.rect.left - (self.screen.get_width() // 5)) // self.maze.block_size def get_nearest_row(self): return (self.rect.top - (self.screen.get_height() // 12)) // self.maze.block_size def is_at_intersection(self): directions = 0 self.tile = (self.get_nearest_row(), self.get_nearest_col()) if self.grid[self.tile[0] - 1][self.tile[1]] not in ('x', ): directions += 1 if self.grid[self.tile[0] + 1][self.tile[1]] not in ('x', ): directions += 1 if self.grid[self.tile[0]][self.tile[1] - 1] not in ('x', ): directions += 1 if self.grid[self.tile[0]][self.tile[1] + 1] not in ('x', ): directions += 1 return True if directions > 2 else False def update(self): if self.state['normal']: if not self.state['blue'] and not self.state['return']: self.update_neutral() elif self.state['blue']: self.update_blue() elif self.state['return']: self.update_return() self.last = (self.rect.centerx, self.rect.centery) def update_neutral(self): options = self.direction_options() if self.is_at_intersection() or self.last == (self.rect.centerx, self.rect.centery): self.direction = self.chase_path(options) if self.direction == 'u' and 'u' in options: self.rect.centery -= self.speed elif self.direction == 'l' and 'l' in options: self.rect.centerx -= self.speed elif self.direction == 'd' and 'd' in options: self.rect.centery += self.speed elif self.direction == 'r' and 'r' in options: self.rect.centerx += self.speed self.set_eyes(self.direction or 'r') self.image = self.normal_frames.next_image() def update_blue(self): self.image = self.blue_frames.next_image() options = self.direction_options() if self.is_at_intersection() or self.last == (self.rect.centerx, self.rect.centery): self.direction = self.flee_path(options) if self.direction == 'u' and 'u' in options: self.rect.centery -= self.speed elif self.direction == 'l' and 'l' in options: self.rect.centerx -= self.speed elif self.direction == 'd' and 'd' in options: self.rect.centery += self.speed elif self.direction == 'r' and 'r' in options: self.rect.centerx += self.speed if abs(self.blue_start - time.get_ticks()) > self.blue_i: self.stop_blue() elif abs(self.blue_start - time.get_ticks()) > int(self.blue_i * 0.5): if self.blink: self.image = self.blue_white.next_image() self.blink = False self.last_blink = time.get_ticks() elif abs(self.last_blink - time.get_ticks()) > self.blink_i: self.blink = True def update_return(self): if abs(self.eat - time.get_ticks()) > self.return_offset: self.image, _ = self.eyes.get_image(key=self.direction) test = self.check_path() if test == '*': self.state['return'] = False self.direction = self.chase_path(self.direction_options()) else: self.direction = self.get_direction() if self.direction == 'u': self.rect.centery -= self.speed elif self.direction == 'l': self.rect.centerx -= self.speed elif self.direction == 'd': self.rect.centery += self.speed elif self.direction == 'r': self.rect.centerx += self.speed def enable(self): options = self.direction_options() self.direction = options[0] self.state['normal'] = True self.sounds.play_loop('std') def disable(self): self.direction = None self.state['normal'] = False self.state['return'] = False self.return_path = None if self.state['blue']: self.stop_blue(resume_audio=False) self.image, _ = self.normal_frames.get_image() self.sounds.stop() def get_direction(self): try: next_step = self.return_path[0] if next_step[0] > self.tile[0]: return 'd' if next_step[0] < self.tile[0]: return 'u' if next_step[1] > self.tile[1]: return 'r' if next_step[1] < self.tile[1]: return 'l' except IndexError as ie: print('Error while trying to get new path direction', ie) return None def direction_options(self): tests = { 'u': self.rect.move((0, -self.speed)), 'l': self.rect.move((-self.speed, 0)), 'd': self.rect.move((0, self.speed)), 'r': self.rect.move((self.speed, 0)) } remove = [] original_pos = self.rect for d, t in tests.items(): self.rect = t if spritecollideany(self, self.maze.maze_blocks) and d not in remove: remove.append(d) if spritecollideany( self, self.target.portals.blue_portal) and d not in remove: remove.append(d) if spritecollideany( self, self.target.portals.orange_portal) and d not in remove: remove.append(d) for rem in remove: del tests[rem] self.rect = original_pos return list(tests.keys()) def chase_path(self, options): pick_direction = None target_pos = (self.target.rect.centerx, self.target.rect.centery) test = (abs(target_pos[0]), abs(target_pos[1])) prefer = test.index(max(test[0], test[1])) if prefer == 0: if target_pos[prefer] < self.rect.centerx: pick_direction = 'l' elif target_pos[prefer] > self.rect.centerx: pick_direction = 'r' else: # y direction if target_pos[prefer] < self.rect.centery: pick_direction = 'u' elif target_pos[prefer] > self.rect.centery: pick_direction = 'd' if pick_direction not in options: if 'u' in options: return 'u' if 'l' in options: return 'l' if 'r' in options: return 'r' if 'd' in options: return 'd' else: return pick_direction def flee_path(self, options): pick_direction = None target_pos = (self.target.rect.centerx, self.target.rect.centery) test = (abs(target_pos[0]), abs(target_pos[1])) prefer = test.index(max(test[0], test[1])) if prefer == 0: if target_pos[prefer] < self.rect.centerx: pick_direction = 'r' elif target_pos[prefer] > self.rect.centerx: pick_direction = 'l' else: if target_pos[prefer] < self.rect.centery: pick_direction = 'd' elif target_pos[prefer] > self.rect.centery: pick_direction = 'u' if pick_direction not in options: if 'u' in options: return 'u' if 'l' in options: return 'l' if 'd' in options: return 'd' if 'r' in options: return 'r' else: return pick_direction def reset(self): self.rect.left, self.rect.top = self.start_position def check_path(self): self.tile = (self.get_nearest_row(), self.get_nearest_col()) if self.return_path and self.tile == self.return_path[0]: del self.return_path[0] if not len(self.return_path) > 0: return '*' return None def set_eyes(self, look_direction): self.image, _ = self.normal_frames.get_image() self.neutral_eye, _ = self.eyes.get_image(key=look_direction) self.image.blit(self.neutral_eye, (0, 0))